Menü
Bedava
kayıt
ev  /  Multimedya/ Örneklerle çok iş parçacıklı programlar. Çok Yönlü Uygulamalar Geliştirmek İçin Sekiz Basit Kural

Örneklerle çok iş parçacıklı programlar. Çok Yönlü Uygulamalar Geliştirmek İçin Sekiz Basit Kural

Çok iş parçacıklı programlama, olaya dayalı grafik kullanıcı arabirimleri yazmaktan ve hatta basit sıralı uygulamalar yazmaktan temelde farklı değildir. Kapsülleme, endişelerin ayrılması, gevşek bağlantı vb. ile ilgili tüm önemli kurallar burada geçerlidir. Ancak birçok geliştirici, tam olarak bu kuralları ihmal ettikleri için çok iş parçacıklı programlar yazmayı zor buluyor. Bunun yerine, yeni başlayanlar için çoklu iş parçacığı programlama metinlerinden derlenen, evreler ve senkronizasyon ilkelleri hakkında çok daha az önemli bilgileri uygulamaya koymaya çalışırlar.

Peki nedir bu kurallar

Bir sorunla karşılaşan başka bir programcı şöyle düşünüyor: "Ah, kesinlikle düzenli ifadeler uygulamamız gerekiyor." Ve şimdi zaten iki sorunu var - Jamie Zawinski.

Bir sorunla karşılaşan başka bir programcı şöyle düşünüyor: "Ah, doğru, burada akışları kullanacağım." Ve şimdi on sorunu var - Bill Schindler.

Goethe'nin baladının kahramanı gibi, çok iş parçacıklı kod yazmayı taahhüt eden çok fazla programcı tuzağa düşüyor " Büyücünün Çırağı". Programcı, prensipte çalışan, ancak er ya da geç kontrolden çıkan ve programcı ne yapacağını bilemeyen bir dizi iş parçacığı oluşturmayı öğrenecektir.

Ancak, sihirbazın terk edilmesinden farklı olarak, talihsiz programcı, asasını sallayacak ve düzeni yeniden kuracak güçlü bir büyücünün gelişini umut edemez. Bunun yerine, programcı sürekli ortaya çıkan sorunlarla başa çıkmaya çalışarak en çirkin numaralara gider. Sonuç hep aynı: Aşırı karmaşık, sınırlı, kırılgan ve güvenilmez bir uygulama elde ediliyor. Kalıcı bir kilitlenme tehdidi ve kötü çok iş parçacıklı kodun doğasında bulunan diğer tehlikeler vardır. Açıklanamayan çökmelerden, düşük performanstan, eksik veya yanlış iş sonuçlarından bahsetmiyorum bile.

Merak etmiş olabilirsiniz: Bu neden oluyor? Yaygın bir yanlış anlama şudur: "Çok iş parçacıklı programlama çok zordur." Ama durum böyle değil. Çok iş parçacıklı bir program güvenilir değilse, genellikle düşük kaliteli tek iş parçacıklı bir programla aynı nedenlerle ters çevrilir. Sadece programcı temel, iyi bilinen ve kanıtlanmış geliştirme yöntemlerini takip etmiyor. Çok iş parçacıklı programlar yalnızca daha karmaşık görünür, çünkü ne kadar çok paralel iş parçacığı yanlış giderse, o kadar fazla karışıklık yaratırlar - ve tek bir iş parçacığından çok daha hızlıdır.

Tek iş parçacıklı kod yazma konusunda profesyonel olarak gelişen, ilk önce çoklu iş parçacığıyla karşılaşan ve onunla baş edemeyen geliştiriciler nedeniyle "çok iş parçacıklı programlamanın karmaşıklığı" hakkındaki yanılgı yaygınlaştı. Ancak önyargılarını ve çalışma alışkanlıklarını yeniden düşünmek yerine, hiçbir şekilde çalışmak istemedikleri gerçeğini inatla sabitliyorlar. Güvenilmez yazılımlar ve kaçırılan teslim tarihleri ​​için bahaneler üreten bu insanlar aynı şeyi tekrarlıyor: "çok iş parçacıklı programlama çok zor."

Lütfen yukarıda çoklu kullanım kullanan tipik programlardan bahsettiğimi unutmayın. Gerçekten de, karmaşık çok iş parçacıklı senaryoların yanı sıra karmaşık tek iş parçacıklı senaryolar da vardır. Ama onlar nadirdir. Kural olarak, pratikte programcıdan doğaüstü hiçbir şey gerekli değildir. Verileri taşır, dönüştürür, zaman zaman bazı hesaplamalar yapar ve son olarak bilgileri bir veritabanına kaydeder veya ekranda görüntüleriz.

Ortalama tek iş parçacıklı programı geliştirmek ve çok iş parçacıklı bir programa dönüştürmek konusunda zor olan hiçbir şey yoktur. En azından olmamalı. Zorluklar iki nedenden dolayı ortaya çıkar:

  • programcılar basit, iyi bilinen kanıtlanmış geliştirme yöntemlerini nasıl uygulayacaklarını bilmiyorlar;
  • Çok iş parçacıklı programlama kitaplarında sunulan bilgilerin çoğu teknik olarak doğrudur, ancak uygulamalı sorunları çözmek için tamamen uygulanamaz.

En önemli programlama kavramları evrenseldir. Tek iş parçacıklı ve çok iş parçacıklı programlara eşit olarak uygulanırlar. Bir akış girdabında boğulan programcılar, tek iş parçacıklı kodda ustalaştıklarında önemli dersler almadılar. Bunu söyleyebilirim çünkü bu tür geliştiriciler çok iş parçacıklı ve tek iş parçacıklı programlarda aynı temel hataları yapıyorlar.

Belki de altmış yıllık programlama tarihinde öğrenilecek en önemli ders şudur: küresel değişken durum- fenalık... Gerçek kötülük. Küresel olarak değişebilir duruma dayanan programlar hakkında akıl yürütmek nispeten zordur ve durumu değiştirmenin çok fazla yolu olduğundan genellikle güvenilmezdir. Bu genel prensibi doğrulayan birçok çalışma yapıldı, asıl amacı bir veya başka bir veri gizleme yöntemini uygulamak olan sayısız tasarım deseni var. Programlarınızı daha öngörülebilir hale getirmek için değiştirilebilir durumu mümkün olduğunca ortadan kaldırmaya çalışın.

Tek iş parçacıklı sıralı bir programda, verilerin bozulma olasılığı, verileri değiştirebilen bileşenlerin sayısı ile doğru orantılıdır.

Kural olarak, küresel durumdan tamamen kurtulmak mümkün değildir, ancak geliştiricinin cephaneliğinde, hangi program bileşenlerinin durumu değiştirebileceğini kesinlikle kontrol etmenizi sağlayan çok etkili araçlar vardır. Ayrıca, ilkel veri yapıları etrafında kısıtlayıcı API katmanlarının nasıl oluşturulacağını öğrendik. Bu nedenle, bu veri yapılarının nasıl değiştiği üzerinde iyi bir kontrole sahibiz.

Küresel olarak değişebilir durum sorunları, 80'lerin sonlarında ve 90'ların başlarında, olay güdümlü programlamanın yaygınlaşmasıyla yavaş yavaş belirgin hale geldi. Programlar artık "baştan" başlamadı veya "sonuna kadar" tek, öngörülebilir bir yürütme yolu izlemedi. Modern programlar, çıktıktan sonra, içlerinde meydana gelen olayların - değişken zaman aralıklarıyla öngörülemeyen bir sırada - bir başlangıç ​​durumuna sahiptir. Kod tek iş parçacıklı kalır, ancak zaman uyumsuz hale gelir. Olayların meydana gelme sırası çok önemli olduğu için verilerin bozulma olasılığı tam olarak artmaktadır. Bu tür durumlar oldukça yaygındır: B olayı A olayından sonra meydana gelirse, o zaman her şey yolunda gider. Ancak A olayı B olayından sonra meydana gelirse ve C olayının aralarına müdahale etmek için zamanı varsa, o zaman veriler tanınmayacak şekilde bozulabilir.

Paralel akışlar söz konusuysa, küresel durumda birkaç yöntem aynı anda çalışabileceğinden sorun daha da ağırlaşır. Küresel devletin tam olarak nasıl değiştiğini yargılamak imkansız hale geliyor. Zaten sadece olayların öngörülemeyen bir sırada gerçekleşebileceği gerçeğinden değil, aynı zamanda birkaç yürütme iş parçacığının durumunun güncellenebileceğinden de bahsediyoruz. eşzamanlı... Asenkron programlama ile, en azından, belirli bir olayın başka bir olay işlemeyi bitirmeden önce gerçekleşmemesini sağlayabilirsiniz. Yani belirli bir olayın işlenmesi sonunda küresel durumun ne olacağını kesin olarak söylemek mümkündür. Çok iş parçacıklı kodda, kural olarak, hangi olayların paralel olarak gerçekleşeceğini söylemek imkansızdır, bu nedenle herhangi bir zamanda küresel durumu kesin olarak tanımlamak imkansızdır.

Kapsamlı küresel olarak değişebilir duruma sahip çok iş parçacıklı bir program, bildiğim Heisenberg belirsizlik ilkesinin en anlamlı örneklerinden biridir. Davranışını değiştirmeden bir programın durumunu kontrol etmek imkansızdır.

Küresel değişebilir durum hakkında başka bir philippic başlattığımda (öz önceki birkaç paragrafta özetleniyor), programcılar gözlerini deviriyor ve tüm bunları uzun zamandır bildiklerine dair beni temin ediyor. Ama bunu biliyorsan, neden kodundan anlayamıyorsun? Programlar, küresel değişebilir durumla doludur ve programcılar kodun neden çalışmadığını merak eder.

Şaşırtıcı olmayan bir şekilde, çok iş parçacıklı programlamadaki en önemli çalışma tasarım aşamasında gerçekleşir. Programın ne yapması gerektiğini net bir şekilde tanımlaması, tüm işlevleri yerine getirecek bağımsız modüller geliştirmesi, hangi modül için hangi verilerin gerekli olduğunu ayrıntılı olarak tanımlaması ve modüller arasında bilgi alışverişinin yollarını belirlemesi gerekmektedir ( Evet, projeye dahil olan herkese güzel tişörtler hazırlamayı unutmayın. İlk şey.- yaklaşık ed. orijinalinde). Bu süreç, tek iş parçacıklı bir program tasarlamaktan temelde farklı değildir. Tek iş parçacıklı kodda olduğu gibi başarının anahtarı, modüller arasındaki etkileşimleri sınırlamaktır. Paylaşılan değişebilir durumdan kurtulabilirseniz, veri paylaşımı sorunları ortaya çıkmaz.

Birisi, bazen küresel devlet olmadan yapmayı mümkün kılacak böylesine hassas bir program tasarımı için zamanın olmadığını iddia edebilir. Buna zaman ayırmanın mümkün ve gerekli olduğuna inanıyorum. Hiçbir şey çok iş parçacıklı programları küresel değişken durumla başa çıkmaya çalışmak kadar yıkıcı bir şekilde etkileyemez. Yönetmeniz gereken daha fazla ayrıntı, programınızın zirveye çıkması ve çökmesi olasılığı daha yüksektir.

Gerçekçi uygulamalarda, değişebilen bir tür ortak durum olmalıdır. Ve çoğu programcının sorun yaşamaya başladığı yer burasıdır. Programcı burada ortak bir durumun gerekli olduğunu görür, çok iş parçacıklı cephaneliğe döner ve oradan en basit aracı alır: evrensel bir kilit (kritik bölüm, muteks veya ne diyorlarsa). Karşılıklı dışlamanın tüm veri paylaşımı sorunlarını çözeceğine inanıyor gibi görünüyorlar.

Böyle tek bir kilitle ortaya çıkabilecek sorunların sayısı şaşırtıcıdır. Yarış koşullarına dikkat edilmelidir, aşırı kapsamlı engelleme ile kapılama sorunları ve tahsis adaleti sorunları sadece birkaç örnektir. Birden fazla kilidiniz varsa, özellikle de iç içe geçmişlerse, kilitlenmelere, dinamik kilitlenmelere, blok kuyruklarına ve diğer eşzamanlılık tehditlerine karşı da önlem almanız gerekir. Ek olarak, içsel tek engelleme sorunları vardır.
Kodu yazdığımda veya gözden geçirdiğimde, neredeyse başarısızlığa karşı güvenli bir temel kuralım var: eğer bir kilit yaptıysanız, görünüşe göre bir yerde hata yapmışsınızdır..

Bu ifade iki şekilde yorumlanabilir:

  1. Kilitlemeye ihtiyacınız varsa, muhtemelen eşzamanlı güncellemelere karşı korumak istediğiniz küresel değişebilir duruma sahipsiniz. Global değişken durumunun varlığı, uygulama tasarımı aşamasında bir kusurdur. Gözden geçirin ve yeniden tasarlayın.
  2. Kilitleri doğru kullanmak kolay değildir ve kilitlemeyle ilgili hataların yerini tespit etmek inanılmaz derecede zor olabilir. Kilidi yanlış kullanmanız çok olasıdır. Bir kilit görürsem ve program alışılmadık bir şekilde davranırsa, yaptığım ilk şey kilide bağlı olan kodu kontrol etmektir. Ve genellikle içinde problemler buluyorum.

Bu yorumların ikisi de doğrudur.

Çok iş parçacıklı kod yazmak kolaydır. Ancak senkronizasyon ilkellerini doğru kullanmak çok, çok zor. Tek bir kilidi bile doğru kullanmaya yetkiniz olmayabilir. Sonuçta, kilitler ve diğer senkronizasyon ilkelleri, tüm sistem düzeyinde dikilmiş yapılardır. Eşzamanlı programlamayı sizden çok daha iyi anlayan kişiler, eşzamanlı veri yapıları ve üst düzey senkronizasyon yapıları oluşturmak için bu ilkelleri kullanır. Ve sen ve ben, sıradan programcılar, sadece bu tür yapıları alıp kodumuzda kullanıyoruz. Bir uygulama programcısı, düşük seviyeli eşitleme ilkellerini aygıt sürücülerine doğrudan çağrı yaptığından daha sık kullanmamalıdır. Yani neredeyse hiç.

Veri paylaşımı sorunlarını çözmek için kilitleri kullanmaya çalışmak, sıvı oksijenle yangını söndürmeye benzer. Bir yangın gibi, bu tür sorunları önlemek, düzeltmekten daha kolaydır. Paylaşılan durumdan kurtulursanız, senkronizasyon ilkellerini de kötüye kullanmanız gerekmez.

Çoklu kullanım hakkında bildiklerinizin çoğu alakasız

Yeni başlayanlar için çoklu kullanımla ilgili eğitimlerde, konuların ne olduğunu öğreneceksiniz. Ardından yazar, bu iş parçacıklarının paralel çalışmasını kurabileceğiniz çeşitli yolları düşünmeye başlayacaktır - örneğin, kilitler ve semaforlar kullanarak paylaşılan verilere erişimi kontrol etmekten bahsedin, olaylarla çalışırken neler olabileceği üzerinde durun. Koşul değişkenlerine, bellek engellerine, kritik bölümlere, mutekslere, uçucu alanlara ve atomik işlemlere yakından bakacaktır. Her türlü sistem işlemini gerçekleştirmek için bu düşük seviyeli yapıların nasıl kullanılacağına dair örneklere bakacağız. Bu materyali yarıya kadar okuduktan sonra, programcı tüm bu ilkeller ve kullanımları hakkında zaten yeterince bilgi sahibi olduğuna karar verir. Sonuçta bu şeyin sistem seviyesinde nasıl çalıştığını biliyorsam uygulama seviyesinde de aynı şekilde uygulayabilirim. Evet?

Bir gence içten yanmalı bir motoru kendi başına nasıl monte edeceğini söylediğinizi hayal edin. Sonra, herhangi bir sürüş eğitimi almadan, onu bir arabanın direksiyonuna geçirip, "Git!" diyorsunuz. Genç, bir arabanın nasıl çalıştığını anlıyor, ancak A noktasından B noktasına nasıl gideceği hakkında hiçbir fikri yok.

İş parçacıklarının sistem düzeyinde nasıl çalıştığını anlamak, genellikle uygulama düzeyinde hiçbir şekilde yardımcı olmaz. Programcıların tüm bu düşük seviyeli ayrıntıları öğrenmesine gerek olmadığını söylemiyorum. Bir iş uygulaması tasarlarken veya geliştirirken bu bilgiyi hemen uygulayabilmeyi beklemeyin.

Giriş niteliğindeki iş parçacığı oluşturma literatürü (ve ilgili akademik dersler) bu tür düşük seviyeli yapıları araştırmamalıdır. En yaygın sorun sınıflarını çözmeye odaklanmanız ve geliştiricilere bu sorunların üst düzey yetenekler kullanılarak nasıl çözüldüğünü göstermeniz gerekir. Prensip olarak, çoğu iş uygulaması son derece basit programlardır. Bir veya daha fazla giriş cihazından veri okurlar, bu veriler üzerinde bazı karmaşık işlemler gerçekleştirirler (örneğin, süreçte biraz daha fazla veri talep ederler) ve ardından sonuçları çıkarırlar.

Bu programlar genellikle yalnızca üç iş parçacığı gerektiren sağlayıcı-tüketici modeline mükemmel şekilde uyar:

  • giriş akışı verileri okur ve onu giriş kuyruğuna koyar;
  • bir işçi iş parçacığı giriş kuyruğundaki kayıtları okur, işler ve sonuçları çıkış kuyruğuna koyar;
  • çıktı akışı, çıktı kuyruğundaki girdileri okur ve bunları depolar.

Bu üç iş parçacığı bağımsız olarak çalışır, aralarındaki iletişim kuyruk düzeyinde gerçekleşir.

Teknik olarak bu kuyruklar, paylaşılan durum bölgeleri olarak düşünülebilse de, pratikte sadece kendi iç senkronizasyonlarının çalıştığı iletişim kanallarıdır. Kuyruklar, birçok üretici ve tüketici ile aynı anda çalışmayı destekler ve bunlara paralel olarak öğe ekleyip kaldırabilirsiniz.

Giriş, işleme ve çıkış aşamaları birbirinden izole olduğundan, bunların uygulanması programın geri kalanını etkilemeden kolayca değiştirilebilir. Kuyruktaki veri türü değişmediği sürece, kendi takdirinize bağlı olarak tek tek program bileşenlerini yeniden düzenleyebilirsiniz. Ayrıca isteğe bağlı sayıda tedarikçi ve tüketici kuyruğa katıldığı için diğer üreticileri/tüketicileri eklemek zor değildir. Aynı kuyruğa bilgi yazan düzinelerce girdi akışımız veya girdi kuyruğundan bilgi alan ve verileri sindiren düzinelerce işçi iş parçacığımız olabilir. Tek bir bilgisayar çerçevesinde, böyle bir model iyi ölçeklenir.

En önemlisi modern programlama dilleri ve kütüphaneleri üretici-tüketici uygulamaları oluşturmayı oldukça kolaylaştırıyor. .NET'te Parallel Collections ve TPL Dataflow Library'yi bulacaksınız. Java, Executor hizmetinin yanı sıra BlockingQueue ve Java.util.concurrent ad alanından diğer sınıflara sahiptir. C++, Boost iş parçacığı kitaplığına ve Intel'in İş Parçacığı Yapı Taşları kitaplığına sahiptir. Microsoft'un Visual Studio 2013'ü asenkron aracılar sunar. Python, JavaScript, Ruby, PHP'de ve bildiğim kadarıyla diğer birçok dilde benzer kütüphaneler var. Bu paketlerden herhangi birini kullanarak, kilitlere, semaforlara, koşul değişkenlerine veya başka herhangi bir senkronizasyon ilkesine başvurmanıza gerek kalmadan bir üretici-tüketici uygulaması oluşturabilirsiniz.

Bu kitaplıklarda çok çeşitli eşitleme ilkelleri serbestçe kullanılır. Bu iyi. Bu kitaplıkların tümü, çoklu iş parçacığını ortalama bir programcıdan kıyaslanamayacak kadar iyi anlayan kişiler tarafından yazılmıştır. Böyle bir kitaplıkla çalışmak, çalışma zamanı dil kitaplığı kullanmakla pratik olarak aynıdır. Assembly dilinden ziyade yüksek seviyeli bir dilde programlama ile karşılaştırılabilir.

Tedarikçi-tüketici modeli birçok örnekten sadece biridir. Yukarıdaki kitaplıklar, düşük seviyeli ayrıntılara girmeden birçok yaygın iş parçacığı tasarım modelini uygulamak için kullanılabilecek sınıfları içerir. İş parçacıklarının tam olarak nasıl koordine ve senkronize edildiği konusunda endişelenmeden büyük ölçekli çok iş parçacıklı uygulamalar oluşturmak mümkündür.

Kitaplıklarla çalışma

Bu nedenle, çok iş parçacıklı programlar oluşturmak, tek iş parçacıklı eşzamanlı programlar yazmaktan temelde farklı değildir. Kapsülleme ve veri gizlemenin önemli ilkeleri evrenseldir ve yalnızca birden çok eşzamanlı iş parçacığı söz konusu olduğunda önem kazanır. Bu önemli hususları ihmal ederseniz, en kapsamlı düşük seviyeli diş açma bilgisi bile sizi kurtarmaz.

Modern geliştiriciler, uygulama programlama düzeyinde birçok sorunu çözmek zorundadır, sistem düzeyinde neler olduğunu düşünmek için zaman yoktur. Uygulamalar ne kadar karmaşık olursa, API seviyeleri arasında o kadar karmaşık ayrıntıların gizlenmesi gerekir. Bunu bir düzineden fazla yıldır yapıyoruz. Sistemin karmaşıklığının programcıdan niteliksel olarak gizlenmesinin, programcının modern uygulamalar yazabilmesinin ana nedeni olduğu iddia edilebilir. Bu nedenle, UI mesaj döngüsünü uygulayarak, düşük seviyeli iletişim protokolleri oluşturarak, vb. sistemin karmaşıklığını gizlemiyor muyuz?

Durum multithreading ile benzerdir. Ortalama bir iş uygulaması programcısının karşılaşabileceği çok iş parçacıklı senaryoların çoğu zaten iyi biliniyor ve kitaplıklarda iyi uygulanıyor. Kütüphane işlevleri, paralelliğin akıllara durgunluk veren karmaşıklığını gizlemede harika bir iş çıkarıyor. Bu kitaplıkları nasıl kullanacağınızı, kullanıcı arabirimi öğelerinin kitaplıklarını, iletişim protokollerini ve sadece çalışan diğer birçok aracı kullandığınız şekilde öğrenmeniz gerekir. Düşük seviyeli çoklu iş parçacığını uzmanlara, yani uygulamaların oluşturulmasında kullanılan kitaplıkların yazarlarına bırakın.

dosyanın sonu. Bu sayede farklı işlemler tarafından yapılan log girişleri hiçbir zaman karıştırılmaz. Daha modern Unix sistemleri, günlük kaydı için özel bir sistem günlüğü hizmeti (3C) sağlar.

Avantajlar:

  1. Geliştirme kolaylığı. Aslında, tek bir iş parçacığı uygulamasının birçok kopyasını çalıştırıyoruz ve bunlar birbirinden bağımsız olarak çalışıyor. Belirli bir çok iş parçacıklı API kullanmamak mümkündür ve süreçler arası iletişim araçları.
  2. Yüksek güvenilirlik. Süreçlerden herhangi birinin anormal şekilde sonlandırılması, geri kalan süreçleri hiçbir şekilde etkilemez.
  3. İyi taşınabilirlik. Uygulama herhangi bir çoklu görev işletim sistemi
  4. Yüksek güvenlik. Farklı uygulama süreçleri farklı kullanıcılar adına çalışabilir. Böylece, süreçlerin her biri yalnızca çalışması için gerekli haklara sahip olduğunda, en az ayrıcalık ilkesini uygulayabilirsiniz. Bazı işlemlerde uzaktan kod yürütülmesine izin veren bir hata bulunsa bile, saldırgan yalnızca bu işlemin yürütüldüğü erişim seviyesini elde edebilecektir.

Dezavantajları:

  1. Tüm uygulamalar bu şekilde sağlanamaz. Örneğin, bu mimari, statik HTML sayfaları sunan bir sunucu için uygundur, ancak bir veritabanı sunucusu ve birçok uygulama sunucusu için hiç uygun değildir.
  2. Süreçleri oluşturmak ve yok etmek pahalı bir işlemdir, bu nedenle bu mimari birçok görev için uygun değildir.

Unix sistemleri, bir süreç oluşturmayı ve bir süreçte yeni bir program başlatmayı mümkün olduğunca ucuz hale getirmek için bir dizi adım atar. Ancak, mevcut bir süreç içinde bir iş parçacığı oluşturmanın her zaman yeni bir süreç oluşturmaktan daha ucuz olacağını anlamalısınız.

Örnekler: apache 1.x (HTTP sunucusu)

Soketler, Borular ve Sistem V IPC İleti Kuyrukları Üzerinden Haberleşen Çok İşlemli Uygulamalar

Listelenen IPC araçları (İşlemler arası iletişim), sözde harmonik işlemler arası iletişim araçlarına atıfta bulunur. Paylaşılan hafızayı kullanmadan süreçlerin ve iş parçacıklarının etkileşimini düzenlemenize izin verirler. Programlama teorisyenleri bu mimariye çok düşkündür çünkü rekabet hataları için birçok seçeneği neredeyse ortadan kaldırır.

Avantajlar:

  1. Göreceli geliştirme kolaylığı.
  2. Yüksek güvenilirlik. İşlemlerden birinin anormal şekilde sonlandırılması, borunun veya soketin kapanmasına neden olur ve mesaj kuyrukları durumunda, mesajların kuyruğa girmesini veya alınmasını durdurur. Uygulamanın geri kalanı bu hatayı kolayca algılayabilir ve hatadan kurtarabilir, belki (ancak zorunlu olarak değil) yalnızca başarısız işlemi yeniden başlatarak.
  3. Bu uygulamaların çoğu (özellikle soket tabanlı olanlar), uygulamanın farklı bileşenlerinin farklı makinelerde çalıştığı dağıtılmış bir ortamda çalışacak şekilde kolayca yeniden tasarlanır.
  4. İyi taşınabilirlik. Uygulama, eski Unix sistemleri de dahil olmak üzere çoğu çok görevli işletim sisteminde çalışacaktır.
  5. Yüksek güvenlik. Farklı uygulama süreçleri farklı kullanıcılar adına çalışabilir. Böylece, süreçlerin her biri yalnızca çalışması için gerekli haklara sahip olduğunda, en az ayrıcalık ilkesini uygulayabilirsiniz.

Bazı işlemlerde uzaktan kod yürütülmesine izin veren bir hata bulunsa bile, saldırgan yalnızca bu işlemin yürütüldüğü erişim seviyesini elde edebilecektir.

Dezavantajları:

  1. Bu mimarinin tüm uygulamalar için tasarlanması ve uygulanması kolay değildir.
  2. Listelenen tüm IPC araçları türleri, seri veri iletimini varsayar. Paylaşılan verilere rastgele erişim gerekiyorsa, bu mimari elverişsizdir.
  3. Bir boru, soket ve mesaj kuyruğu aracılığıyla veri aktarımı, sistem çağrılarının yürütülmesini ve verilerin iki kez kopyalanmasını gerektirir - önce orijinal işlemin adres alanından çekirdeğin adres alanına, ardından çekirdeğin adres alanından belleğe hedef süreç... Bunlar pahalı işlemlerdir. Büyük miktarda veri aktarırken bu ciddi bir sorun haline gelebilir.
  4. Çoğu sistemde toplam boru, priz ve IPC tesislerinin sayısı sınırlıdır. Örneğin, Solaris varsayılan olarak işlem başına maksimum 1.024 açık boru, soket ve dosyaya izin verir (belirli sistem çağrısının sınırlamaları nedeniyle). Solaris mimari sınırı, işlem başına 65.536 boru, soket ve dosyadır.

    TCP / IP soketlerinin toplam sayısındaki sınır, ağ arayüzü başına 65536'dan fazla değildir (TCP başlıklarının formatı nedeniyle). System V IPC mesaj kuyrukları, çekirdek adres alanında bulunur, bu nedenle sistemdeki kuyruk sayısı ve aynı anda kuyruğa alınan mesajların miktarı ve sayısı üzerinde kesin sınırlar vardır.

  5. Bir süreç yaratmak ve yok etmek ve süreçler arasında geçiş yapmak pahalı işlemlerdir. Bu mimari her durumda optimal değildir.

Paylaşılan bellek çoklu işlem uygulamaları

Paylaşılan bellek, System V IPC paylaşılan belleği ve dosyadan belleğe eşleme olabilir. Erişimi senkronize etmek için System V IPC semaforlarını, mutekslerini ve POSIX semaforlarını kullanabilir ve dosyaları belleğe eşlerken dosyanın bölümlerini yakalayabilirsiniz.

Avantajlar:

  1. Paylaşılan verilere verimli rastgele erişim. Bu mimari, veritabanı sunucularının uygulanması için uygundur.
  2. Yüksek tolerans. System V IPC'yi destekleyen veya taklit eden herhangi bir işletim sistemine taşınabilir.
  3. Nispeten yüksek güvenlik. Farklı uygulama süreçleri farklı kullanıcılar adına çalışabilir. Böylece, süreçlerin her biri yalnızca çalışması için gerekli haklara sahip olduğunda, en az ayrıcalık ilkesini uygulayabilirsiniz. Ancak, erişim düzeylerinin ayrılması, daha önce düşünülen mimarilerdeki kadar katı değildir.

Dezavantajları:

  1. Geliştirmenin göreceli karmaşıklığı. Erişim senkronizasyon hatalarını - sözde çekişme hataları - test sırasında tespit etmek çok zordur.

    Bu, tek iş parçacıklı veya daha basit çoklu görev mimarilerine göre toplam geliştirme maliyetinde 3 ila 5 kat artışla sonuçlanabilir.

  2. Düşük güvenilirlik. Herhangi bir uygulama işleminin anormal şekilde sonlandırılması, paylaşılan hafızayı tutarsız bir durumda bırakabilir (ve genellikle bırakır).

    Bu genellikle uygulamanın geri kalanının çökmesine neden olur. Lotus Domino gibi bazı uygulamalar, herhangi biri anormal şekilde sonlandırıldığında, sunucu genelindeki süreçleri kasıtlı olarak öldürür.

  3. Bir süreç yaratmak ve yok etmek ve bunlar arasında geçiş yapmak pahalı işlemlerdir.

    Bu nedenle, bu mimari tüm uygulamalar için uygun değildir.

  4. Belirli koşullar altında, paylaşılan belleğin kullanılması ayrıcalıkların artmasına neden olabilir. Uzaktan kod yürütülmesine yol açan işlemlerden birinde bir hata bulunursa, bir saldırganın uygulamanın diğer işlemlerinde uzaktan kod yürütmek için bunu kullanması çok olasıdır.

    Diğer bir deyişle, en kötü senaryoda, bir saldırgan, uygulama süreçlerinin erişim düzeylerinin en yükseğine karşılık gelen erişim düzeyini elde edebilir.

  5. Paylaşılan bellek uygulamaları aynı fiziksel bilgisayarda veya en azından paylaşılan RAM'e sahip makinelerde çalışmalıdır. Aslında, örneğin bellek eşlemeli paylaşılan dosyalar kullanılarak bu sınırlama aşılabilir, ancak bu önemli bir ek yük getirir.

Aslında, bu mimari, çok işlemli ve çok iş parçacıklı uygulamaların dezavantajlarını uygun şekilde birleştirir. Ancak, 80'lerde ve 90'ların başında, Unix'in standartlaştırılmış çok iş parçacıklı API'lerinden önce geliştirilen bir dizi popüler uygulama bu mimariyi kullanır. Bunlar, hem ticari (Oracle, DB2, Lotus Domino), hem de Sendmail'in ve diğer bazı posta sunucularının ücretsiz, modern sürümleri olan birçok veritabanı sunucusudur.

Çok iş parçacıklı uygulamalar uygun

Bir uygulamanın iş parçacıkları veya iş parçacıkları tek bir işlem içinde çalışır. Bir işlemin tüm adres alanı iş parçacıkları arasında paylaşılır. İlk bakışta, bu, herhangi bir özel API'ler olmadan iş parçacıkları arasındaki etkileşimi düzenlemenize izin veriyor gibi görünüyor. Gerçekte, durum böyle değildir - birkaç iş parçacığı paylaşılan bir veri yapısı veya sistem kaynağı ile çalışıyorsa ve iş parçacıklarından en az biri bu yapıyı değiştirirse, zaman içinde bazı noktalarda veriler tutarsız olacaktır.

Bu nedenle, iş parçacıkları etkileşimi düzenlemek için özel araçlar kullanmalıdır. En önemli araçlar, karşılıklı dışlama ilkelleridir (muteksler ve okuma/yazma kilitleri). Bu temel öğeleri kullanarak, programcı tutarsız bir durumdayken hiçbir iş parçacığının paylaşılan kaynaklara erişmemesini sağlayabilir (buna karşılıklı dışlama denir). System V IPC, yalnızca paylaşılan bellek segmentinde tahsis edilen yapılar paylaşılır. Düzenli değişkenler ve normal olarak tahsis edilen dinamik veri yapıları, her süreç için farklıdır). Paylaşılan veri erişim hatalarını - rekabet hatalarını - test sırasında tespit etmek çok zordur.

  • Madde 1 nedeniyle uygulama geliştirme ve hata ayıklamanın yüksek maliyeti.
  • Düşük güvenilirlik. Arabellek taşmaları veya işaretçi hataları gibi veri yapılarının yok edilmesi, bir işlemdeki tüm iş parçacıklarını etkiler ve genellikle tüm işlemin anormal bir şekilde sonlandırılmasına neden olur. İş parçacıklarının birinde sıfıra bölme gibi diğer önemli hatalar da genellikle işlemdeki tüm iş parçacıklarının çökmesine neden olur.
  • Düşük güvenlik. Tüm uygulama iş parçacıkları tek bir işlemde, yani aynı kullanıcı adına ve aynı erişim haklarına sahip olarak çalışır. Minimum gerekli ayrıcalıklar ilkesini uygulamak mümkün değildir, işlemin, uygulamanın tüm iş parçacıklarının gerektirdiği tüm işlemleri gerçekleştirebilen kullanıcı adına yürütülmesi gerekir.
  • Bir iş parçacığının oluşturulması hala oldukça pahalı bir işlemdir. Her iş parçacığı için, varsayılan olarak 32 bit mimarilerde 1 megabayt RAM ve 64 bit mimarilerde 2 megabayt ve diğer bazı kaynakları kaplayan kendi yığını mutlaka tahsis edilir. Bu nedenle, bu mimari tüm uygulamalar için uygun değildir.
  • Uygulamayı çok makineli bir bilgi işlem sisteminde çalıştıramama. Paylaşılan dosyaları belleğe eşlemek gibi önceki bölümde bahsedilen teknikler, çok iş parçacıklı bir programa uygulanamaz.
  • Genel olarak, çok iş parçacıklı uygulamaların, paylaşılan bellek kullanan çok işlemli uygulamalarla hemen hemen aynı avantaj ve dezavantajlara sahip olduğunu söyleyebiliriz.

    Bununla birlikte, çok iş parçacıklı bir uygulamayı çalıştırmanın maliyeti daha düşüktür ve böyle bir uygulamanın geliştirilmesi, bazı açılardan paylaşılan belleğe dayalı bir uygulamaya göre daha kolaydır. Bu nedenle, son yıllarda çok iş parçacıklı uygulamalar giderek daha popüler hale geldi.

    Bölüm 10.

    Çok iş parçacıklı uygulamalar

    Modern işletim sistemlerinde çoklu görev hafife alınır [ Apple OS X'ten önce, Macintosh bilgisayarlarda modern çoklu görev işletim sistemleri yoktu. Tam teşekküllü çoklu göreve sahip bir işletim sistemini uygun şekilde tasarlamak çok zordur, bu nedenle OS X'in Unix sistemine dayanması gerekiyordu.]. Kullanıcı, metin düzenleyici ve posta istemcisi aynı anda başlatıldığında bu programların çakışmamasını ve e-posta alırken editörün çalışmayı bırakmamasını bekler. Aynı anda birkaç program başlatıldığında, işletim sistemi programlar arasında hızla geçiş yapar ve onlara sırayla bir işlemci sağlar (tabii ki bilgisayarda birden fazla işlemci kurulu değilse). Sonuç olarak, yanılsama aynı anda birden fazla program çalıştırabilir, çünkü en iyi daktilo (ve en hızlı internet bağlantısı) bile modern bir işlemciye ayak uyduramaz.

    Çoklu iş parçacığı, bir anlamda çoklu görevin bir sonraki düzeyi olarak görülebilir: farklı öğeler arasında geçiş yapmak yerine. programlar işletim sistemi aynı programın farklı bölümleri arasında geçiş yapar. Örneğin, çok iş parçacıklı bir e-posta istemcisi, yeni mesajları okurken veya oluştururken yeni e-posta mesajları almanızı sağlar. Günümüzde, çoklu kullanım da birçok kullanıcı tarafından kabul edilmektedir.

    VB hiçbir zaman normal çoklu kullanım desteğine sahip olmadı. Doğru, çeşitlerinden biri VB5'te ortaya çıktı - işbirlikçi akış modeli(apartman ipi). Birazdan göreceğiniz gibi, işbirlikçi model programcıya çoklu iş parçacığının bazı faydalarını sağlar, ancak tüm özelliklerden tam olarak faydalanmaz. Er ya da geç, bir eğitim makinesinden gerçek bir makineye geçmeniz gerekir ve VB .NET, ücretsiz çok iş parçacıklı bir modeli destekleyen VB'nin ilk sürümü oldu.

    Ancak çoklu okuma, programlama dillerinde kolayca uygulanan ve programcılar tarafından kolayca hakim olunan özelliklerden biri değildir. Niye ya?

    Çünkü çok iş parçacıklı uygulamalarda, beklenmedik bir şekilde ortaya çıkan ve kaybolan çok zor hatalar meydana gelebilir (ve bu tür hataların ayıklanması en zor olanlardır).

    Bir uyarı: çoklu kullanım, programlamanın en zor alanlarından biridir. En ufak bir dikkatsizlik, düzeltilmesi astronomik toplamlar alan zor hataların ortaya çıkmasına neden olur. Bu nedenle, bu bölüm birçok kötüörnekler - bunları kasıtlı olarak yaygın hataları gösterecek şekilde yazdık. Bu, çok iş parçacıklı programlamayı öğrenmek için en güvenli yaklaşımdır: İlk bakışta her şey yolunda gidiyormuş gibi göründüğünde olası sorunları tespit edebilmeli ve bunları nasıl çözeceğinizi bilmelisiniz. Çok iş parçacıklı programlama tekniklerini kullanmak istiyorsanız, onsuz yapamazsınız.

    Bu bölüm, daha fazla bağımsız çalışma için sağlam bir temel oluşturacaktır, ancak çok iş parçacıklı programlamayı tüm incelikleriyle açıklayamayacağız - yalnızca İş Parçacığı ad alanının sınıflarıyla ilgili basılı belgeler 100 sayfadan fazladır. Çok iş parçacıklı programlamada daha yüksek düzeyde ustalaşmak istiyorsanız, özel kitaplara bakın.

    Ancak multithreading programlama ne kadar tehlikeli olursa olsun bazı problemlerin profesyonel çözümü için vazgeçilmezdir. Programlarınız uygun olduğunda çoklu iş parçacığı kullanmıyorsa, kullanıcılar çok sinirlenecek ve başka bir ürünü tercih edeceklerdir. Örneğin, popüler e-posta programı Eudora'nın yalnızca dördüncü sürümünde, çok iş parçacıklı yeteneklerin ortaya çıkması, e-posta ile çalışmak için herhangi bir modern programın hayal edilmesinin imkansız olduğuydu. Eudora çoklu kullanım desteğini sunduğunda, birçok kullanıcı (bu kitabın yazarlarından biri de dahil olmak üzere) diğer ürünlere geçmişti.

    Son olarak, .NET'te tek iş parçacıklı programlar basitçe mevcut değildir. Her şey.NET programları çok iş parçacıklıdır, çünkü çöp toplayıcı düşük öncelikli bir arka plan işlemi olarak çalışır. Aşağıda gösterildiği gibi, .NET'te ciddi grafik programlama için, uygun iş parçacığı oluşturma, program uzun işlemler yürütürken grafik arabirimin kilitlenmesini önlemeye yardımcı olur.

    Çoklu kullanım ile tanışın

    Her program belirli bir bağlam, kod ve verilerin bellekteki dağılımını açıklayan. Bağlamı kaydederek, program akışının durumu gerçekten kaydedilir, bu da gelecekte onu geri yüklemenize ve program yürütmesine devam etmenize olanak tanır.

    Bağlam kaydetme, zaman ve bellek maliyeti ile birlikte gelir. İşletim sistemi program iş parçacığının durumunu hatırlar ve denetimi başka bir iş parçacığına aktarır. Program, askıya alınan iş parçacığını yürütmeye devam etmek istediğinde, kaydedilen bağlamın geri yüklenmesi gerekir ve bu daha da uzun sürer. Bu nedenle, çoklu kullanım yalnızca faydaları tüm maliyetleri karşıladığında kullanılmalıdır. Bazı tipik örnekler aşağıda listelenmiştir.

    • Programın işlevselliği, e-posta alma ve yeni ileti hazırlama örneğinde olduğu gibi, açık ve doğal olarak çeşitli heterojen işlemlere bölünmüştür.
    • Program uzun ve karmaşık hesaplamalar yapar ve hesaplamalar süresince grafik arayüzün bloke edilmesini istemezsiniz.
    • Program, birden çok işlemcinin kullanımını destekleyen bir işletim sistemine sahip çok işlemcili bir bilgisayarda çalışır (etkin iş parçacığı sayısı işlemci sayısını geçmediği sürece, paralel yürütme, iş parçacığı değiştirmeyle ilişkili maliyetlerden pratik olarak ücretsizdir).

    Çok iş parçacıklı programların mekaniğine geçmeden önce, çok iş parçacıklı programlama alanında yeni başlayanlar arasında sıklıkla kafa karışıklığına neden olan bir duruma işaret etmek gerekir.

    Program akışında bir nesne değil bir prosedür yürütülecektir.

    "Nesne çalışıyor" ifadesiyle ne kastedildiğini söylemek zor, ancak yazarlardan biri genellikle çoklu iş parçacığı programlama konusunda seminerler veriyor ve bu soru diğerlerinden daha sık soruluyor. Belki biri program iş parçacığının çalışmasının sınıfın New yöntemine yapılan bir çağrıyla başladığını ve ardından iş parçacığının ilgili nesneye iletilen tüm mesajları işlediğini düşünüyor. Bu tür temsiller kesinlikle yanılıyorlar. Bir nesne, farklı (ve hatta bazen aynı) yöntemleri yürüten birkaç iş parçacığı içerebilirken, nesnenin mesajları birkaç farklı iş parçacığı tarafından iletilir ve alınır (bu arada, bu, çok iş parçacıklı programlamayı karmaşıklaştıran nedenlerden biridir: Bir programda hata ayıklamak için, belirli bir anda hangi iş parçacığının bu veya bu prosedürü gerçekleştirdiğini bulmanız gerekir!).

    İplikler, nesnelerin yöntemlerinden oluşturulduğundan, nesnenin kendisi genellikle iş parçacığından önce oluşturulur. Nesneyi başarıyla oluşturduktan sonra program, nesnenin yönteminin adresini ileterek bir iş parçacığı oluşturur ve ancak bundan sonra iş parçacığının yürütülmesine başlama emri verir. İş parçacığının oluşturulduğu prosedür, tüm prosedürler gibi, yeni nesneler oluşturabilir, mevcut nesneler üzerinde işlemler gerçekleştirebilir ve kapsamındaki diğer prosedürleri ve işlevleri çağırabilir.

    Program iş parçacıklarında ortak sınıf yöntemleri de yürütülebilir. Bu durumda, başka bir önemli durumu da unutmayın: iş parçacığı, oluşturulduğu prosedürden bir çıkışla sona erer. Prosedürden çıkılana kadar program akışının normal şekilde tamamlanması mümkün değildir.

    İplikler sadece doğal olarak değil, anormal şekilde de sonlanabilir. Bu genellikle tavsiye edilmez. Daha fazla bilgi için Akışları Sonlandırma ve Kesme bölümüne bakın.

    Programatik iş parçacıklarının kullanımıyla ilgili temel .NET özellikleri, İş Parçacığı ad alanında yoğunlaşmıştır. Bu nedenle, çok iş parçacıklı programların çoğu aşağıdaki satırla başlamalıdır:

    System.Threading'i içe aktarır

    Bir ad alanını içe aktarmak, programınızın yazılmasını kolaylaştırır ve IntelliSense teknolojisini etkinleştirir.

    Akışların prosedürlerle doğrudan bağlantısı, bu resimde önemli olduğunu göstermektedir. delegeler(bkz. bölüm 6). Özellikle, Threading ad alanı, genellikle program dizileri başlatılırken kullanılan ThreadStart temsilcisini içerir. Bu temsilciyi kullanmak için sözdizimi şöyle görünür:

    Genel Delege Alt ThreadStart ()

    ThreadStart temsilcisiyle çağrılan kodun hiçbir parametresi veya dönüş değeri olmamalıdır, bu nedenle işlevler (bir değer döndüren) ve parametreli prosedürler için iş parçacıkları oluşturulamaz. Akıştan bilgi aktarmak için ayrıca alternatif yollar aramanız gerekir, çünkü yürütülen yöntemler değer döndürmez ve referansla aktarımı kullanamaz. Örneğin, ThreadMethod WilluseThread sınıfındaysa ThreadMethod, WillUseThread sınıfının örneklerinin özelliklerini değiştirerek bilgi iletebilir.

    Uygulama alanları

    .NET iş parçacıkları, belgelerde "uygulamanın içinde çalıştığı sanal alan" olarak tanımlanan uygulama etki alanlarında çalışır. Bir uygulama etki alanı, Win32 işlemlerinin hafif bir sürümü olarak düşünülebilir; tek bir Win32 işlemi, birden çok uygulama etki alanı içerebilir. Uygulama etki alanları ve işlemler arasındaki temel fark, bir Win32 işleminin kendi adres alanına sahip olmasıdır (belgelerde uygulama etki alanları ayrıca fiziksel bir işlem içinde çalışan mantıksal işlemlerle karşılaştırılır). NET'te, tüm bellek yönetimi çalışma zamanı tarafından gerçekleştirilir, böylece birden çok uygulama etki alanı tek bir Win32 işleminde çalışabilir. Bu şemanın faydalarından biri, uygulamaların gelişmiş ölçeklendirme yetenekleridir. Uygulama etki alanlarıyla çalışmaya yönelik araçlar, AppDomain sınıfındadır. Bu sınıfın belgelerini incelemenizi öneririz. Yardımı ile programınızın çalıştığı ortam hakkında bilgi alabilirsiniz. Özellikle, .NET sistem sınıflarında yansıma gerçekleştirilirken AppDomain sınıfı kullanılır. Aşağıdaki program yüklenen derlemeleri listeler.

    System.Reflection'ı içe aktarır

    Modül Modülü

    Alt Ana ()

    AppDomain Olarak Alan Adını Karartın

    theDomain = AppDomain.CurrentDomain

    Dim Montajları () As

    Montajlar = theDomain.GetAssemblies

    Dim anAssemblyxAs

    Montajlardaki Her Bir Montaj İçin

    Console.WriteLinetanAssembly.Tam Ad) Sonraki

    Console.ReadLine ()

    Alt Alt

    Modülü Bitir

    Akış oluşturma

    İlkel bir örnekle başlayalım. Sonsuz bir döngüde sayaç değerini azaltan ayrı bir iş parçacığında bir prosedür çalıştırmak istediğinizi varsayalım. Prosedür, sınıfın bir parçası olarak tanımlanır:

    Genel Sınıf WillUseThreads

    Sayaçtan Genel Çıkar ()

    Tamsayı olarak karartma sayısı

    Doğru iken yap - = 1

    Console.WriteLlne ("Başka bir iş parçacığındayım ve sayaç ="

    & saymak)

    Döngü

    Alt Alt

    Sınıfı Bitir

    Do döngüsü koşulu her zaman doğru olduğundan, SubtractFromCounter yordamının yürütülmesine hiçbir şeyin müdahale etmeyeceğini düşünebilirsiniz. Ancak, çok iş parçacıklı bir uygulamada durum her zaman böyle değildir.

    Aşağıdaki kod parçacığı, iş parçacığını başlatan Alt Ana yordamını ve İçe Aktarma komutunu içerir:

    Option Strict On Imports System.Threading Module Module

    Alt Ana ()

    1 Dim myTest As New WillUseThreads ()

    2 Dim bThreadStart As New ThreadStart (AddressOf _

    myTest.SubtractFromCounter)

    3 Dim bThread As New Thread (bThreadStart)

    4" bThread.Başlat ()

    Dim i Tamsayı Olarak

    5 Doğruyken Yapın

    Console.WriteLine ("Ana iş parçacığında ve sayım" & i) i + = 1

    Döngü

    Alt Alt

    Modülü Bitir

    En önemli noktalara sırayla bir göz atalım. Her şeyden önce, Sub Man n prosedürü her zaman çalışır. ana akım(ana iplik). .NET programlarında her zaman çalışan en az iki iş parçacığı vardır: ana iş parçacığı ve çöp toplama iş parçacığı. Satır 1, test sınıfının yeni bir örneğini oluşturur. 2. satırda, bir ThreadStart temsilcisi oluşturuyoruz ve 1. satırda oluşturulan test sınıfı örneğinin SubtractFromCounter prosedürünün adresini iletiyoruz (bu prosedüre parametresiz denir). İyiThreading ad alanını içe aktararak, uzun ad atlanabilir. Yeni iş parçacığı nesnesi 3. satırda oluşturulur. Thread sınıfı yapıcısını çağırırken ThreadStart temsilcisinin geçtiğine dikkat edin. Bazı programcılar bu iki satırı tek bir mantıksal satırda birleştirmeyi tercih eder:

    Dim bThread As New Thread (New ThreadStarttAddressOf _

    myTest.SubtractFromCounter))

    Son olarak, 4. satır, ThreadStart temsilcisi için oluşturulan Thread örneğinin Start yöntemini çağırarak iş parçacığını "başlatır". Bu yöntemi çağırarak, işletim sistemine Çıkarma prosedürünün ayrı bir iş parçacığında çalışması gerektiğini söyleriz.

    Önceki paragraftaki "başlar" sözcüğü tırnak içine alınmıştır, çünkü bu çok iş parçacıklı programlamanın birçok tuhaflığından biridir: Başlat'ı çağırmak aslında diziyi başlatmaz! Yalnızca işletim sistemine belirtilen iş parçacığını çalışacak şekilde programlamasını söyler, ancak doğrudan başlatma programın kontrolü dışındadır. İşletim sistemi her zaman iş parçacıklarının yürütülmesini kontrol ettiğinden, iş parçacıklarını kendi başınıza yürütmeye başlayamazsınız. Daha sonraki bir bölümde, işletim sisteminin iş parçacığınızı daha hızlı başlatmasını sağlamak için önceliği nasıl kullanacağınızı öğreneceksiniz.

    İncirde. 10.1, bir programı başlattıktan ve ardından Ctrl + Break tuşuyla kesintiye uğrattıktan sonra neler olabileceğine dair bir örnek gösterir. Bizim durumumuzda, yeni bir iş parçacığı ancak ana iş parçacığındaki sayaç 341'e yükseldikten sonra başladı!

    Pirinç. 10.1. Basit çok iş parçacıklı yazılım çalışma zamanı

    Program daha uzun bir süre boyunca çalışırsa, sonuç Şekil 1'de gösterilene benzer bir şekilde görünecektir. 10.2. görüyoruz ki sençalışan iş parçacığının tamamlanması askıya alınır ve kontrol tekrar ana iş parçacığına aktarılır. Bu durumda, bir tezahür var zaman dilimleme yoluyla önleyici çoklu iş parçacığı. Bu ürkütücü terimin anlamı aşağıda açıklanmıştır.

    Pirinç. 10.2. Basit bir çok iş parçacıklı programda iş parçacıkları arasında geçiş yapma

    İş parçacıklarını keserken ve denetimi diğer iş parçacıklarına aktarırken, işletim sistemi zaman dilimleme yoluyla önleyici çoklu iş parçacığı ilkesini kullanır. Zaman niceleme ayrıca daha önce çok iş parçacıklı programlarda ortaya çıkan yaygın sorunlardan birini çözer - bir iş parçacığı tüm CPU zamanını alır ve diğer iş parçacıklarının kontrolünden daha düşük değildir (kural olarak, bu, yukarıdaki gibi yoğun döngülerde olur). Özel CPU ele geçirmesini önlemek için, iş parçacıklarınız kontrolü zaman zaman diğer iş parçacıklarına aktarmalıdır. Programın "bilinçsiz" olduğu ortaya çıkarsa, biraz daha az arzu edilen başka bir çözüm daha vardır: işletim sistemi, öncelik düzeyi ne olursa olsun, çalışan bir iş parçacığını her zaman önceler, böylece sistemdeki her iş parçacığına işlemciye erişim verilir.

    .NET çalıştıran tüm Windows sürümlerinin nicemleme şemaları her bir iş parçacığı için minimum bir zaman dilimine sahip olduğundan, .NET programlamasında CPU'ya özel ele geçirme sorunları o kadar ciddi değildir. Öte yandan, .NET çerçevesi başka sistemler için uyarlanırsa, bu değişebilir.

    Start'ı çağırmadan önce programımıza aşağıdaki satırı eklersek, en düşük önceliğe sahip iş parçacıkları bile CPU süresinin bir kısmını alacaktır:

    bThread.Priority = ThreadPriority.En Yüksek

    Pirinç. 10.3. En yüksek önceliğe sahip iş parçacığı genellikle daha hızlı başlar

    Pirinç. 10.4. İşlemci ayrıca daha düşük öncelikli iş parçacıkları için sağlanır

    Komut, yeni iş parçacığına maksimum önceliği atar ve ana iş parçacığının önceliğini azaltır. Şek. 10.3 Yeni iş parçacığının eskisinden daha hızlı çalışmaya başladığı görülebilir, ancak Şekil 1'deki gibi. 10.4, ana iş parçacığı da kontrolü alırtembellik (çok kısa bir süre için de olsa ve yalnızca çıkarma ile akışın uzun süreli çalışmasından sonra). Programı bilgisayarlarınızda çalıştırdığınızda, Şekil 1'de gösterilenlere benzer sonuçlar elde edeceksiniz. 10.3 ve 10.4, ancak sistemlerimiz arasındaki farklılıklar nedeniyle tam bir eşleşme olmayacaktır.

    ThreadPrlority numaralandırılmış türü, beş öncelik düzeyi için değerler içerir:

    ThreadPriority.En Yüksek

    ThreadPriority.AboveNormal

    ThreadPrilority.Normal

    ThreadPriority.BelowNormal

    ThreadPriority.En düşük

    birleştirme yöntemi

    Bazen bir program iş parçacığının başka bir iş parçacığı bitene kadar duraklatılması gerekir. Diyelim ki iş parçacığı 2 hesaplamasını tamamlayana kadar iş parçacığı 1'i duraklatmak istiyorsunuz. Bunun için akış 1'den Akış 2 için Join yöntemi çağrılır. Başka bir deyişle, komut

    thread2.Join ()

    mevcut iş parçacığını askıya alır ve iş parçacığı 2'nin tamamlanmasını bekler. kilitli hali.

    Join yöntemini kullanarak akış 1'i akış 2'ye bağlarsanız, işletim sistemi akış 2'den sonra akış 1'i otomatik olarak başlatır. kararsız:İş parçacığı 2'nin bitmesinden tam olarak ne kadar süre sonra iş parçacığı 1'in çalışmaya başlayacağını söylemek imkansızdır.Bir boole değeri döndüren başka bir Join sürümü vardır:

    thread2.Join (Tamsayı)

    Bu yöntem ya iş parçacığı 2'nin tamamlanmasını bekler ya da belirtilen zaman aralığı geçtikten sonra iş parçacığı 1'in engellemesini kaldırarak işletim sistemi zamanlayıcısının iş parçacığına yeniden CPU zamanı ayırmasına neden olur. Yöntem, akış 2 belirtilen zaman aşımı aralığı sona ermeden önce sona ererse True, aksi halde False döndürür.

    Temel kuralı hatırlayın: 2. iş parçacığının tamamlanıp tamamlanmadığını veya zaman aşımına uğradığını, iş parçacığı 1'in ne zaman etkinleştirileceğini kontrol edemezsiniz.

    Konu adları, CurrentThread ve ThreadState

    Thread.CurrentThread özelliği, o anda yürütülmekte olan iş parçacığı nesnesine bir başvuru döndürür.

    VB .NET'te çok iş parçacıklı uygulamalarda hata ayıklamak için aşağıda açıklanan harika bir iş parçacığı penceresi olmasına rağmen, genellikle komut bize yardımcı oldu.

    MsgBox (Thread.CurrentThread.Name)

    Oldukça sık, kodun yürütülmesi gereken tamamen farklı bir iş parçacığında yürütüldüğü ortaya çıkıyor.

    "Program akışlarının deterministik olmayan çizelgelenmesi" teriminin çok basit bir anlama geldiğini hatırlayın: programcının pratikte zamanlayıcının çalışmasını etkilemek için elinde hiçbir araç yoktur. Bu nedenle, programlar bir iş parçacığının geçerli durumu hakkında bilgi döndürmek için genellikle ThreadState özelliğini kullanır.

    Akışlar penceresi

    Visual Studio .NET'in Threads penceresi, çok iş parçacıklı programların hatalarını ayıklamada çok değerlidir. Kesinti modunda Debug> Windows alt menü komutu ile etkinleştirilir. Aşağıdaki komutla bThread dizisine bir ad atadığınızı varsayalım:

    bThread.Name = "İş parçacığı çıkarma"

    Programı Ctrl + Break tuş kombinasyonuyla (veya başka bir şekilde) kestikten sonra akışlar penceresinin yaklaşık bir görünümü Şek. 10.5.

    Pirinç. 10.5. Akışlar penceresi

    İlk sütundaki ok, Thread.CurrentThread özelliği tarafından döndürülen etkin iş parçacığını işaretler. Kimlik sütunu, sayısal iş parçacığı kimliklerini içerir. Sonraki sütun, akış adlarını (atanmışsa) listeler. Location sütunu çalıştırılacak prosedürü gösterir (örneğin, Şekil 10.5'te Console sınıfının WriteLine prosedürü). Kalan sütunlar, öncelikli ve askıya alınmış diziler hakkında bilgi içerir (sonraki bölüme bakın).

    İş parçacığı penceresi (işletim sistemi değil!) Bağlam menülerini kullanarak programınızın iş parçacıklarını kontrol etmenizi sağlar. Örneğin, ilgili satıra sağ tıklayarak ve Dondur komutunu seçerek mevcut diziyi durdurabilirsiniz (durdurulan diziye daha sonra devam edebilirsiniz). Durdurma iş parçacıkları genellikle hata ayıklama sırasında hatalı çalışan bir iş parçacığının uygulamaya müdahale etmesini önlemek için kullanılır. Ek olarak, akışlar penceresi başka bir (durdurulmamış) akışı etkinleştirmenize olanak tanır; bunu yapmak için, gerekli satıra sağ tıklayın ve içerik menüsünden İpliğe Geç komutunu seçin (veya iplik hattına çift tıklayın). Aşağıda gösterileceği gibi, bu, olası kilitlenmeleri teşhis etmede çok faydalıdır.

    Bir akışı askıya alma

    Geçici olarak kullanılmayan akışlar, Slеer yöntemi kullanılarak pasif bir duruma aktarılabilir. Pasif bir akış da engellenmiş olarak kabul edilir. Elbette, bir iş parçacığı pasif duruma getirildiğinde, iş parçacıklarının geri kalanı daha fazla işlemci kaynağına sahip olacaktır. Slеer yöntemi için standart sözdizimi aşağıdaki gibidir: Thread.Sleep (interval_in_milisaniye)

    Uyku çağrısının bir sonucu olarak, aktif iş parçacığı en az belirli bir milisaniye sayısı boyunca pasif hale gelir (ancak, belirtilen aralık sona erdikten hemen sonra etkinleştirme garanti edilmez). Lütfen unutmayın: Yöntem çağrılırken, belirli bir iş parçacığına başvuru iletilmez - Uyku yöntemi yalnızca etkin iş parçacığı için çağrılır.

    Sleep'in başka bir sürümü, mevcut iş parçacığının ayrılan CPU süresinin geri kalanından vazgeçmesini sağlar:

    Konu.Uyku (0)

    Sonraki seçenek, mevcut iş parçacığını sınırsız bir süre için pasif duruma getirir (etkinleştirme yalnızca Interrupt'u aradığınızda gerçekleşir):

    Thread.Slеer (Zaman aşımı.Sonsuz)

    Pasif diziler (sınırsız bir zaman aşımı olsa bile) Interrupt yöntemiyle kesintiye uğrayabildiğinden, bu da istisna durumunda ThreadlnterruptExcepti'nin başlatılmasına yol açar, Slayer çağrısı aşağıdaki parçada olduğu gibi her zaman bir Try-Catch bloğunun içine alınır:

    Denemek

    Thread.Sleep (200)

    "İş parçacığının pasif durumu kesintiye uğradı

    e'yi İstisna Olarak Yakala

    "Diğer istisnalar

    Denemeyi Bitir

    Her .NET programı bir program iş parçacığı üzerinde çalışır, bu nedenle Sleep yöntemi programları askıya almak için de kullanılır (Threadipg ad alanı program tarafından içe aktarılmamışsa, tam nitelikli Threading.Thread. Sleep adını kullanmanız gerekir).

    Program dizilerini sonlandırma veya kesintiye uğratma

    ThreadStart temsilcisi oluşturulduğunda belirtilen yöntem olduğunda bir iş parçacığı otomatik olarak sonlandırılır, ancak bazen belirli faktörler meydana geldiğinde yöntemi (ve dolayısıyla iş parçacığını) sonlandırmak gerekir. Bu gibi durumlarda, akışlar genellikle koşullu değişken, hangi durumda olduğuna bağlıakıştan acil çıkış hakkında bir karar verilir. Tipik olarak, bunun için prosedüre bir Do-While döngüsü dahildir:

    Alt DişliYöntem ()

    "Program, anket için araçlar sağlamalıdır.

    "koşullu değişken.

    "Örneğin, bir koşullu değişken, bir özellik olarak biçimlendirilebilir.

    Şart Değişkeni Yaparken Yap = Yanlış Ve Daha FazlaWorkToDo

    "Ana kod

    Döngü Sonu Alt

    Koşullu değişkeni yoklamak biraz zaman alır. Bir iş parçacığının zamanından önce sona ermesini bekliyorsanız, yalnızca bir döngü koşulunda kalıcı yoklamayı kullanmalısınız.

    Koşul değişkeninin belirli bir konumda kontrol edilmesi gerekiyorsa, sonsuz bir döngü içinde Exit Sub ile birlikte If-Then komutunu kullanın.

    Koşullu bir değişkene erişim, diğer iş parçacıklarından maruz kalmanın normal kullanımını engellememesi için senkronize edilmelidir. Bu önemli konu, "Sorun Giderme: Senkronizasyon" bölümünde ele alınmaktadır.

    Ne yazık ki, pasif (veya başka bir şekilde engellenmiş) iş parçacıklarının kodu yürütülmez, bu nedenle koşullu bir değişkeni yoklama seçeneği onlar için uygun değildir. Bu durumda, istenen iş parçacığına bir başvuru içeren nesne değişkeninde Interrupt yöntemini çağırın.

    Kesinti yöntemi yalnızca Bekleme, Uyku veya Katılma durumundaki iş parçacıklarında çağrılabilir. Listelenen durumlardan birinde olan bir iş parçacığı için Interrupt'u çağırırsanız, bir süre sonra iş parçacığı yeniden çalışmaya başlar ve yürütme ortamı, iş parçacığında istisnada bir ThreadlnterruptedExcepti başlatır. Bu, thread Thread.Sleepdimeout çağrılarak süresiz olarak pasifleştirilmiş olsa bile oluşur. Sonsuz). "Bir süre sonra" deriz çünkü iş parçacığı planlaması deterministik değildir. ThreadlnterruptedExcepti istisnası, bekleme durumundan çıkış kodunu içeren Catch bölümü tarafından yakalanır. Bununla birlikte, bir Kesme çağrısında iş parçacığını sonlandırmak için Yakalama bölümü gerekli değildir - iş parçacığı özel durumu uygun gördüğü şekilde işler.

    .NET'te, engellenmemiş iş parçacıkları için bile Interrupt yöntemi çağrılabilir. Bu durumda, iş parçacığı en yakın engellemede kesilir.

    Konuları askıya alma ve öldürme

    İş parçacığı ad alanı, normal iş parçacığı oluşturmayı kesen diğer yöntemleri içerir:

    • Askıya almak;
    • İptal et.

    .NET'in neden bu yöntemler için destek içerdiğini söylemek zor - Suspend ve Abort'u aradığınızda, program büyük olasılıkla kararsız hale gelecektir. Yöntemlerin hiçbiri akışın normal olarak sıfırlanmasına izin vermez. Ayrıca, Suspend veya Abort'u aradığınızda, iş parçacığının askıya alındıktan veya durdurulduktan sonra nesneleri hangi durumda bırakacağını tahmin edemezsiniz.

    Abort'u çağırmak bir ThreadAbortException oluşturur. Bu garip istisnanın neden programlarda ele alınmaması gerektiğini anlamanıza yardımcı olmak için, .NET SDK belgelerinden bir alıntı:

    “... Bir iş parçacığı Abort çağrılarak yok edildiğinde, çalışma zamanı bir ThreadAbortException oluşturur. Bu, program tarafından yakalanamayan özel bir istisna türüdür. Bu istisna atıldığında, çalışma zamanı iş parçacığını sonlandırmadan önce tüm Son bloklarını çalıştırır. Son bloklarda herhangi bir işlem yapılabileceğinden, akışın yok edildiğinden emin olmak için Katıl'ı arayın.

    Ahlaki: İptal Et ve Askıya Al önerilmez (ve hala Askıya Alma olmadan yapamıyorsanız, Resume yöntemini kullanarak askıya alınan diziye devam edin). Bir iş parçacığını yalnızca senkronize edilmiş bir koşul değişkenini yoklayarak veya yukarıda tartışılan Kesme yöntemini çağırarak güvenli bir şekilde sonlandırabilirsiniz.

    Arka plan konuları (daemon'lar)

    Arka planda çalışan bazı iş parçacıkları, diğer program bileşenleri durduğunda otomatik olarak çalışmayı durdurur. Özellikle, çöp toplayıcı arka plan iş parçacıklarından birinde çalışır. Arka plan iş parçacıkları genellikle veri almak için oluşturulur, ancak bu yalnızca diğer iş parçacıkları alınan verileri işleyebilen kod çalıştırıyorsa yapılır. Sözdizimi: akış adı.IsBackGround = True

    Uygulamada yalnızca arka planda kalan iş parçacıkları varsa, uygulama otomatik olarak sonlandırılacaktır.

    Daha büyük bir örnek: HTML kodundan veri çıkarmak

    Akışları yalnızca programın işlevselliği açıkça birkaç işleme bölündüğünde kullanmanızı öneririz. Bölüm 9'daki HTML çıkarıcı buna iyi bir örnektir.Sınıfımız iki şey yapar: Amazon'dan veri almak ve onu işlemek. Bu, çok iş parçacıklı programlamanın gerçekten uygun olduğu bir duruma mükemmel bir örnektir. Birkaç farklı kitap için sınıflar oluşturuyoruz ve ardından verileri farklı akışlarda ayrıştırıyoruz. Her kitap için yeni bir iş parçacığı oluşturmak programın verimliliğini artırır, çünkü bir iş parçacığı veri alırken (Amazon sunucusunda beklemeyi gerektirebilir), başka bir iş parçacığı önceden alınmış verileri işlemekle meşgul olacaktır.

    Bu programın çok iş parçacıklı sürümü, yalnızca birkaç işlemcili bir bilgisayarda veya ek verilerin alınması analizleriyle etkili bir şekilde birleştirilebiliyorsa, tek iş parçacıklı sürümünden daha verimli çalışır.

    Yukarıda bahsedildiği gibi, threadlerde sadece parametresi olmayan prosedürler çalıştırılabilir, bu yüzden programda küçük değişiklikler yapmanız gerekecektir. Aşağıda, parametreleri hariç tutmak için yeniden yazılan temel prosedür verilmiştir:

    Genel Alt FindRank ()

    m_Rank = ScrapeAmazon ()

    Console.WriteLine ("sıralaması" & m_Name & "Is" & GetRank)

    Alt Alt

    Birleşik alanı bilgi depolamak ve almak için kullanamayacağımız için (grafik arayüzlü çok kanallı programların yazılması bu bölümün son bölümünde tartışılmaktadır), program dört kitabın verilerini bir dizide saklar, tanımı şöyle başlar:

    Dim theBook (3.1) As String theBook (0.0) = "1893115992"

    theBook (0.l) = "VB .NET Programlama" "Vb.

    AmazonRanker nesnelerinin oluşturulduğu aynı döngüde dört akış oluşturulur:

    i = 0 ila 3 için

    Denemek

    theRanker = Yeni AmazonRanker (TheBook (i.0). theBookd.1))

    aThreadStart = Yeni ThreadStar (AddressOf theRanker.FindRan ()

    aThread = Yeni Konu (aThreadStart)

    aThread.Name = Kitap (i.l)

    aThread.Start () Catch e As İstisna

    Console.WriteLine (e.Mesaj)

    Denemeyi Bitir

    Sonraki

    Programın tam metni aşağıdadır:

    Seçenek Sıkı İthalat System.IO İthalat System.Net

    System.Threading'i içe aktarır

    Modül Modülü

    Alt Ana ()

    Kitabı (3.1) Dize Olarak Karartın

    Kitap (0.0) = "1893115992"

    theBook (0.l) = "VB .NET Programlama"

    Kitap (l.0) = "1893115291"

    theBook (l.l) = "Veritabanı Programlama VB .NET"

    Kitap (2,0) = "1893115623"

    theBook (2.1) = "Programcı" s C#'a Giriş. "

    Kitap (3.0) = "1893115593"

    theBook (3.1) = ".Net Platformunu Besle"

    Dim i Tamsayı Olarak

    Dim theRanker As = AmazonRanker

    Threading.ThreadStart olarak aThreadStart'ı karartın

    Threading.Thread Olarak Konuyu Karartın

    i = 0 ila 3 için

    Denemek

    theRanker = Yeni AmazonRankerttheBook (i.0). Kitap (i.1))

    aThreadStart = Yeni ThreadStart (Ranker Adresi. FindRank)

    aThread = Yeni Konu (aThreadStart)

    aThread.Name = Kitap (i.l)

    aThread.Start ()

    e'yi İstisna Olarak Yakala

    Console.WriteLlnete.Message)

    Bitir Sonraki Dene

    Console.ReadLine ()

    Alt Alt

    Modülü Bitir

    Genel Sınıf AmazonRanker

    Dize Olarak Özel m_URL

    Tamsayı Olarak Özel m_Rank

    Dize Olarak Özel m_Name

    Public Sub New (ByVal ISBN As String. ByVal theName As String)

    m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

    m_Name = theName End Sub

    Genel Alt FindRank () m_Rank = ScrapeAmazon ()

    Console.Writeline ("sıralaması" & m_Name & "is"

    & GetRank) End Sub

    Genel Salt Okunur Özellik GetRank () As String Get

    m_Rank ise<>0 Sonra

    Geri Dön CStr (m_Rank) Aksi

    "Sorunlar

    Bitir

    Bitir

    Mülkü Sonlandır

    Genel Salt Okunur Özellik GetName () As String Get

    m_Name döndür

    Bitir

    Mülkü Sonlandır

    Özel İşlev ScrapeAmazon () As Integer Try

    URL'yi Yeni Uri Olarak Karart (m_URL)

    WebRequest Olarak İsteği Karartın

    theRequest = WebRequest.Create (theURL)

    WebResponse Olarak Yanıtı Karartın

    theResponse = theRequest.GetResponse

    Bir Okuyucuyu Yeni StreamReader Olarak Karartın (theResponse.GetResponseStream ())

    Dize Olarak Verileri Karartın

    theData = aReader.ReadToEnd

    İade Analizi (Veriler)

    E'yi İstisna Olarak Yakala

    Console.WriteLine (E.Mesaj)

    Console.WriteLine (E.StackTrace)

    Konsol. Okuma Hattı ()

    Bitir Dene Bitir İşlevi

    Özel İşlev Analizi (Dize Olarak Verileri Değerlendir) Tamsayı Olarak

    Lokasyon As.Integer Location = theData.IndexOf (" Amazon.com

    Satış Sıralaması:") _

    + "Amazon.com Satış Sıralaması:".Uzunluk

    Dize olarak loş sıcaklık

    Data.Substring (Location.l) = "<" temp = temp

    & theData.Substring (Location.l) Konum + = 1 Döngü

    Dönüş Clnt (sıcaklık)

    Bitiş İşlevi

    Sınıfı Bitir

    Çok iş parçacıklı işlemler .NET ve G/Ç ad alanlarında yaygın olarak kullanılır, bu nedenle .NET Framework kitaplığı onlar için özel zaman uyumsuz yöntemler sağlar. Çok iş parçacıklı programlar yazarken zaman uyumsuz yöntemleri kullanma hakkında daha fazla bilgi için HTTPWebRequest sınıfının BeginGetResponse ve EndGetResponse yöntemlerine bakın.

    Ana tehlike (genel veriler)

    Şimdiye kadar, iş parçacıkları için tek güvenli kullanım durumu kabul edildi - akışlarımız genel verileri değiştirmedi. Genel verilerde değişikliğe izin verirseniz, olası hatalar katlanarak artmaya başlar ve program için bunlardan kurtulmak çok daha zor hale gelir. Öte yandan, farklı iş parçacıkları tarafından paylaşılan verilerin değiştirilmesini yasaklarsanız, çok iş parçacıklı .NET programlaması VB6'nın sınırlı yeteneklerinden pek farklı olmayacaktır.

    Gereksiz ayrıntılara girmeden ortaya çıkan sorunları gösteren küçük bir program sunuyoruz. Bu program, her odasında termostat bulunan bir evi simüle eder. Sıcaklık, hedef sıcaklıktan 5 derece Fahrenheit veya daha fazla (yaklaşık 2.77 santigrat derece) daha düşükse, ısıtma sistemine sıcaklığı 5 derece artırmasını emrediyoruz; aksi takdirde sıcaklık sadece 1 derece yükselir. Mevcut sıcaklık ayarlanandan büyük veya eşitse, herhangi bir değişiklik yapılmaz. Her odadaki sıcaklık kontrolü 200 milisaniye gecikmeli ayrı bir akışla gerçekleştirilir. Ana çalışma aşağıdaki snippet ile yapılır:

    Eğer mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

    Thread.Sleep (200)

    ThreadlinterruptedException olarak kravat yakala

    "Pasif bekleme kesintiye uğradı

    e'yi İstisna Olarak Yakala

    "Diğer Son Deneme İstisnaları

    mHouse.HouseTemp + - 5 "Vb.

    Programın tam kaynak kodu aşağıdadır. Sonuç, Şekil 2'de gösterilmektedir. 10.6: Evin sıcaklığı 105 derece Fahrenheit'e (40.5 santigrat derece) ulaştı!

    1 Seçenek Kesin Açık

    2 İthalat System.Threading

    3 Modül Modülü

    4 Alt Ana ()

    5 Dim myHouse As New House (l0)

    6 Konsol. Okuma Hattı ()

    7 Bitiş Alt

    8 Bitiş Modülü

    9 Kamu Sınıfı Evi

    10 Public Const MAX_TEMP As Integer = 75

    Tamsayı olarak 11 Özel mCurTemp = 55

    12 Özel mOda () Oda Olarak

    13 Genel Alt Yeni (ByVal numOfRooms As Integer)

    14 ReDim mOda (numOfOda = 1)

    15 Dim i Tamsayı Olarak

    16 İplik Başlangıcını İş Parçacığı Olarak Karartın.İş Parçacığı Başlangıcı

    17 İpliği İplik Olarak Karartmak

    18 i = 0 için numOfRooms -1 için

    19 Deneyin

    20 mRooms (i) = NewRoom (Ben, mCurTemp, CStr (i) & "throom")

    21 aThreadStart - Yeni ThreadStart (AddressOf _

    mRooms (i) .CheckTempInRoom)

    22 aThread = Yeni Konu (aThreadStart)

    23 aThread.Start ()

    24 E'yi İstisna Olarak Yakala

    25 Console.WriteLine (E.StackTrace)

    26 Denemeyi Bitir

    27 Sonraki

    28 Bitiş Alt

    29 Kamu Mülkü HouseTemp () Tamsayı Olarak

    otuz. Elde etmek

    31 Dönüş mCurTemp

    32 Son Al

    33 Set (Tamsayı Olarak ByVal Değeri)

    34 mCurTemp = Değer 35 Son Ayar

    36 Son Özellik

    37 Son Sınıf

    38 Genel Sınıf Odası

    39 Tam Sayı Olarak Özel mCurTemp

    Dize Olarak 40 Özel mName

    41 Özel mHouse Ev Olarak

    42 Kamu Alt Yeni (ByVal theHouse As House,

    ByVal temp As Integer, ByVal roomName As String)

    43 mEv = Ev

    44 mCurTemp = sıcaklık

    45 mName = odaAdı

    46 Son Alt

    47 Genel Alt CheckTempInRoom ()

    48 Sıcaklığı Değiştir ()

    49 Bitiş Alt

    50 Özel Alt DeğişimSıcaklık ()

    51 Deneyin

    52 Eğer mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

    53 İplik.Uyku (200)

    54 mHouse.HouseSıcaklık + - 5

    55 Console.WriteLine ("İçerdeyim" & Me.mName & _

    56 ".Mevcut sıcaklık" & mHouse.HouseTemp)

    57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

    58 Thread.Sleep (200)

    59 mHouse.HouseTemp + = 1

    60 Console.WriteLine ("İçerideyim" & Me.mName & _

    61 ".Mevcut sıcaklık" & mHouse.HouseTemp)

    62 Diğer

    63 Console.WriteLine ("İçerdeyim" & Me.mName & _

    64 ".Mevcut sıcaklık" & mHouse.HouseTemp)

    65 "Hiçbir şey yapmayın, sıcaklık normal

    66 Bitir

    67 ThreadlinterruptedException olarak yakalama tae

    68 "Pasif bekleme kesintiye uğradı

    69 İstisna Olarak Yakala

    70 "Diğer istisnalar

    71 Son Deneme

    72 Bitiş Alt

    73 Son Sınıf

    Pirinç. 10.6. Çoklu kullanım sorunları

    Alt Ana prosedür (4-7. satırlar), on "oda" ile bir "ev" yaratır. House sınıfı, maksimum 75 derece Fahrenheit (yaklaşık 24 santigrat derece) sıcaklık ayarlar. 13-28. satırlar oldukça karmaşık bir ev inşaatçısını tanımlar. Programı anlamanın anahtarı 18-27. satırlardır. Satır 20, başka bir oda nesnesi oluşturur ve ev nesnesine bir başvuru, oda nesnesinin gerekirse ona başvurabilmesi için yapıcıya iletilir. 21-23 numaralı satırlar, her odadaki sıcaklığı ayarlamak için on akış başlatır. Room sınıfı, 38-73. satırlarda tanımlanmıştır. Ev coxpa referansıRoom sınıfı yapıcısındaki mHouse değişkeninde depolanır (satır 43). Sıcaklığı kontrol etme ve ayarlama kodu (50-66. satırlar) basit ve doğal görünüyor, ancak yakında göreceğiniz gibi, bu izlenim aldatıcı! Program Sleep yöntemini kullandığından, bu kodun bir Try-Catch bloğuna sarıldığını unutmayın.

    Neredeyse hiç kimse 105 derece Fahrenheit (40.5 ila 24 santigrat derece) sıcaklıklarda yaşamayı kabul etmez. Ne oldu? Sorun aşağıdaki satırla ilgilidir:

    Eğer mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

    Ve şu oluyor: İlk önce akış 1 ile sıcaklık kontrol ediliyor. Sıcaklığın çok düşük olduğunu görüyor ve 5 derece yükseltiyor. Ne yazık ki, sıcaklık yükselmeden önce, akış 1 kesilir ve kontrol akış 2'ye aktarılır. Akış 2, aynı değişkeni kontrol eder. henüz değiştirilmedi akış 1. Böylece, akış 2 de sıcaklığı 5 derece artırmaya hazırlanıyor, ancak bunu yapacak zamanı yok ve aynı zamanda bekleme durumuna giriyor. İşlem, akış 1 etkinleştirilene kadar devam eder ve bir sonraki komuta geçer - sıcaklığı 5 derece arttırır. Artış, 10 akışın tamamı etkinleştirildiğinde tekrarlanır ve ev sakinleri kötü vakit geçirir.

    Sorunun çözümü: senkronizasyon

    Önceki programda, programın sonucunun iş parçacıklarının yürütüldüğü sıraya bağlı olduğu bir durum ortaya çıkar. Ondan kurtulmak için, aşağıdaki gibi komutların olduğundan emin olmanız gerekir.

    Eğer mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

    kesilmeden önce aktif iş parçacığı tarafından tamamen işlenir. Bu özellik denir atomik utanç - bir kod bloğu, bir atomik birim olarak her iş parçacığı tarafından kesintisiz olarak yürütülmelidir. Bir atom bloğunda birleştirilen bir grup komut, tamamlanana kadar iş parçacığı zamanlayıcı tarafından kesilemez. Herhangi bir çok iş parçacıklı programlama dilinin atomiteyi sağlamanın kendi yolları vardır. VB .NET'te, SyncLock komutunu kullanmanın en kolay yolu, çağrıldığında bir nesne değişkeni iletmektir. Önceki örnekteki ChangeTemperature prosedüründe küçük değişiklikler yapın, program düzgün çalışacaktır:

    Özel Alt Sıcaklık Değişimi () SyncLock (mHouse)

    Denemek

    Eğer mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

    Thread.Sleep (200)

    mHouse.HouseTemp + = 5

    Console.WriteLine ("İçerdeyim" & Me.mName & _

    ".Mevcut sıcaklık" & mHouse.HouseTemp)

    kendi

    mHouse.HouseSıcaklık< mHouse. MAX_TEMP Then

    Thread.Sleep (200) mHouse.HouseTemp + = 1

    Console.WriteLine ("İçerideyim" & Me.mName & _ ".Mevcut sıcaklık" & mHouse.HomeTemp) Diğer

    Console.WriteLineC "İçerideyim" & Me.mName & _ ".Mevcut sıcaklık" & mHouse.HouseTemp)

    "Hiçbir şey yapmayın, sıcaklık normal

    Bağlantıyı Yakala As ThreadlnterruptedException

    "Pasif bekleme, Catch e As Exception tarafından kesintiye uğradı

    "Diğer istisnalar

    Denemeyi Bitir

    SyncLock'u sonlandır

    Alt Alt

    SyncLock blok kodu atomik olarak yürütülür. İlk iş parçacığı, End SyncLock komutuyla kilidi serbest bırakana kadar, diğer tüm iş parçacıklarına erişim kapatılacaktır. Senkronize bir bloktaki bir iş parçacığı pasif bir bekleme durumuna girerse, iş parçacığı kesilene veya devam ettirilene kadar kilit kalır.

    SyncLock komutunun doğru kullanımı, program iş parçacığınızı güvende tutar. Ne yazık ki, SyncLock'un aşırı kullanımının performans üzerinde olumsuz bir etkisi vardır. Kodu çok iş parçacıklı bir programda senkronize etmek, çalışmasının hızını birkaç kat azaltır. Yalnızca ihtiyacınız olan kodu senkronize edin ve mümkün olan en kısa sürede kilidi serbest bırakın.

    Temel koleksiyon sınıfları, çok iş parçacıklı uygulamalarda iş parçacığı açısından güvenli değildir, ancak .NET Framework, koleksiyon sınıflarının çoğunun iş parçacığı açısından güvenli sürümlerini içerir. Bu sınıflarda, potansiyel olarak tehlikeli yöntemlerin kodu SyncLock bloklarında bulunur. Koleksiyon sınıflarının iş parçacığı güvenli sürümleri, veri bütünlüğünün tehlikeye girdiği her yerde çok iş parçacıklı programlarda kullanılmalıdır.

    Koşullu değişkenlerin SyncLock komutu kullanılarak kolayca uygulanabileceğini belirtmek gerekir. Bunu yapmak için, yazma işlemini aşağıdaki parçada yapıldığı gibi, okuma ve yazma için mevcut olan ortak boole özelliğine senkronize etmeniz yeterlidir:

    Genel Sınıf KoşuluDeğişken

    Nesne Olarak Özel Paylaşılan dolap = Yeni Nesne ()

    Özel Paylaşılan mOK Olarak Boolean Paylaşılan

    Özellik TheConditionVariable () As Boolean

    Elde etmek

    mOK'yi döndür

    Bitir

    Ayarla (ByVal Değeri Boolean Olarak) SyncLock (dolap)

    mOK = Değer

    SyncLock'u sonlandır

    Bitiş Seti

    Mülkü Sonlandır

    Sınıfı Bitir

    SyncLock Komut ve İzleme Sınıfı

    Yukarıdaki basit örneklerde gösterilmeyen SyncLock komutunun kullanılmasıyla ilgili bazı incelikler vardır. Bu nedenle, senkronizasyon nesnesinin seçimi çok önemli bir rol oynar. Önceki programı SyncLock (mHouse) yerine SyncLock (Me) komutuyla çalıştırmayı deneyin. Sıcaklık yine eşiğin üzerine çıkıyor!

    SyncLock komutunun şunu kullanarak senkronize olduğunu unutmayın: nesne, kod parçacığı tarafından değil, parametre olarak iletilir. SyncLock parametresi, diğer iş parçacıklarından senkronize edilmiş parçaya erişmek için bir kapı görevi görür. SyncLock (Ben) komutu aslında birkaç farklı "kapı" açar, bu da tam olarak senkronizasyonla kaçınmaya çalıştığınız şeydi. ahlak:

    Çok iş parçacıklı bir uygulamada paylaşılan verileri korumak için, SyncLock komutunun her seferinde bir nesneyi senkronize etmesi gerekir.

    Senkronizasyon belirli bir nesneyle ilişkili olduğundan, bazı durumlarda yanlışlıkla diğer parçaları kilitlemek mümkündür. Diyelim ki, her ikisi de bigLock nesnesinde senkronize edilen birinci ve ikinci olmak üzere iki senkronize yönteminiz var. İş parçacığı 1 önce yönteme girdiğinde ve bigLock'u aldığında, erişim zaten iş parçacığı 1 ile sınırlı olduğundan hiçbir iş parçacığı ikinci yönteme giremez!

    SyncLock komutunun işlevselliği, Monitor sınıfının işlevselliğinin bir alt kümesi olarak düşünülebilir. Monitor sınıfı son derece özelleştirilebilir ve önemsiz olmayan senkronizasyon görevlerini çözmek için kullanılabilir. SyncLock komutu, Moni tor sınıfının Enter ve Exi t yöntemlerinin yakın bir analogudur:

    Denemek

    Monitor.Enter (Nesne) Sonunda

    Monitor.Exit (Nesne)

    Denemeyi Bitir

    Bazı standart işlemler için (bir değişkeni artırma/azaltma, iki değişkenin içeriğini değiştirme), .NET Framework, yöntemleri bu işlemleri atomik düzeyde gerçekleştiren Interlocked sınıfını sağlar. Interlocked sınıfını kullanarak, bu işlemler SyncLock komutunu kullanmaktan çok daha hızlıdır.

    birbirine kenetlenen

    Senkronizasyon sırasında, kilit iş parçacıklarına değil nesnelere ayarlanır, bu nedenle kullanırken farklı engellenecek nesneler farklı programlardaki kod parçaları bazen oldukça önemsiz hatalar meydana gelir. Ne yazık ki, çoğu durumda, tek bir nesne üzerinde senkronizasyon, iş parçacıklarının çok sık engellenmesine yol açacağından, basitçe kabul edilemez.

    durumu göz önünde bulundurun birbirine kenetlenen(kilitlenme) en basit haliyle. Yemek masasında iki programcı düşünün. Ne yazık ki, sadece bir bıçakları ve iki kişilik bir çatalları var. Yemek için hem bıçağa hem de çatala ihtiyacınız olduğunu varsayarsak, iki durum mümkündür:

    • Bir programcı eline bir bıçak ve çatal almayı başarır ve yemeye başlar. Dolu olduğunda, akşam yemeğini bir kenara koyar ve sonra başka bir programcı onları alabilir.
    • Bir programcı bıçağı, diğeri çatalı alır. Diğeri aletini bırakmadıkça ikisi de yemeye başlayamaz.

    Çok iş parçacıklı bir programda bu duruma denir karşılıklı engellemeİki yöntem farklı nesneler üzerinde senkronize edilir. İş parçacığı A, nesne 1'i yakalar ve bu nesne tarafından korunan program bölümüne girer. Ne yazık ki, çalışması için farklı bir eşitleme nesnesine sahip başka bir Eşitleme Kilidi tarafından korunan koda erişmesi gerekiyor. Ancak başka bir nesne tarafından senkronize edilmiş bir parçaya girme zamanı gelmeden, B akışı ona girer ve bu nesneyi yakalar. Şimdi iplik A ikinci parçaya giremez, iplik B ilk parçaya giremez ve her iki iplik de süresiz olarak beklemeye mahkumdur. Gerekli nesne hiçbir zaman serbest bırakılmayacağı için hiçbir iş parçacığı çalışmaya devam edemez.

    Kilitlenmelerin teşhisi, nispeten nadir durumlarda ortaya çıkabilmeleri nedeniyle karmaşıktır. Her şey, zamanlayıcının CPU zamanını onlara tahsis ettiği sıraya bağlıdır. Çoğu durumda, senkronizasyon nesnelerinin kilitlenmemiş bir sırada yakalanması mümkündür.

    Aşağıdaki, az önce açıklanan kilitlenme durumunun bir uygulamasıdır. En temel noktaların kısa bir tartışmasından sonra, iş parçacığı penceresinde bir kilitlenme durumunun nasıl tanımlanacağını göstereceğiz:

    1 Seçenek Kesin Açık

    2 İthalat System.Threading

    3 Modül Modülü

    4 Alt Ana ()

    5 Dim Tom Yeni Programcı Olarak ("Tom")

    6 Dim Bob Yeni Programcı Olarak ("Bob")

    7 Konuyu Karart Yeni Konu Başlangıcı Olarak Başlat (Tom.Eat'in Adresi)

    8 Konuyu Yeni Konu Olarak Karart (aThreadStart)

    9 aThread.Name = "Tom"

    10 Dim bThreadAs New ThreadStarttAddressOf Bob.Eat)

    11 Dim bThread As New Thread (bThreadStart)

    12 bThread.Name = "Bob"

    13 aThread.Start ()

    14 bThread.Başlat ()

    15 Bitiş Alt

    16 Uç Modülü

    17 Genel Sınıf Çatalı

    18 Özel Paylaşılan mForkAvaiTable As Boolean = True

    19 Özel Paylaşılan mOwner As String = "Hiç kimse"

    20 Özel Salt Okunur Özellik OwnsUtensil () As String

    21 Al

    22 İade Sahibi

    23 Son Al

    24 Son Özellik

    25 Public Sub GrabForktByVal a As Programmer)

    26 Console.Writel_ine (Thread.CurrentThread.Name & _

    "çatalı almaya çalışıyorum.")

    27 Console.WriteLine (Me.OwnsUtensil & "çatal var."). ...

    28 Monitor.Enter (Me) "SyncLock (aFork)"

    29 mFork Varsa

    30 a.HasFork = Doğru

    31 mSahibi = a.BenimAdım

    32 mÇatalMevcut = YANLIŞ

    33 Console.WriteLine (a.MyName & "çatalı yeni aldım.bekliyor")

    34 Deneyin

    Thread.Sleep (100) Catch e As Exception Console.WriteLine (e.StackTrace)

    Denemeyi Bitir

    35 Bitir

    36 Monitor.Exit (Ben)

    SyncLock'u sonlandır

    37 Son Alt

    38 Son Sınıf

    39 Genel Sınıf Bıçağı

    40 Özel Paylaşılan mKnifeAvailable As Boolean = True

    41 Özel Paylaşılan mOwner As String = "Hiç kimse"

    42 Özel Salt Okunur Özellik OwnsUtensi1 () As String

    43 Al

    44 İade Sahibi

    45 Son Al

    46 Son Özellik

    47 Public Sub GrabKnifetByVal As Programcı)

    48 Console.WriteLine (Thread.CurrentThread.Name & _

    "bıçağı almaya çalışıyorum."

    49 Console.WriteLine (Me.OwnsUtensil & "bıçağı var.")

    50 Monitor.Enter (Me) "SyncLock (aKnife)"

    51 mKnife Varsa

    52 mKnifeAvailable = Yanlış

    53 a.HasKnife = Doğru

    54 mSahibi = a.BenimAdım

    55 Console.WriteLine (a.MyName & "bıçak aldım.bekliyor")

    56 Deneyin

    Thread.Sleep (100)

    e'yi İstisna Olarak Yakala

    Console.WriteLine (e.StackTrace)

    Denemeyi Bitir

    57 Bitir

    58 Monitor.Exit (Ben)

    59 Bitiş Alt

    60 Son Sınıf

    61 Kamu Sınıfı Programcısı

    62 Özel mName As String

    63 Özel Paylaşılan mFork As Fork

    64 Özel Paylaşımlı mKnife As Knife

    Boolean Olarak 65 Özel mHasKnife

    Boolean Olarak 66 Özel mHasFork

    67 Paylaşılan Alt Yeni ()

    68 mFork = Yeni Çatal ()

    69 mBıçak = Yeni Bıçak ()

    70 Bitiş Alt

    71 Public Sub New (ByVal theName As String)

    72 mName = theName

    73 Son Alt

    74 Genel Salt Okunur Özellik MyName () As String

    75

    76 mName döndür

    77 Son Al

    78 Son Özellik

    79 Kamu Mülkü HasKnife () Boolean Olarak

    80 Al

    81 Dönüş mHasKnife

    82 Son Al

    83 Set (ByVal Değeri Boolean Olarak)

    84 mHasKnife = Değer

    85 Uç Seti

    86 Son Özellik

    87 Kamu Mülkü HasFork () Boolean Olarak

    88 Al

    89 Dönüş mHasFork

    90 Son Al

    91 Set (ByVal Değeri Boolean Olarak)

    92 mHasFork = Değer

    93 Uç Seti

    94 Son Özellik

    95 Genel Alt Yemek ()

    96 Bana Kadar Yap.HasKnife And Me.HasFork

    97 Console.Writeline (Thread.CurrentThread.Name & "iş parçacığında.")

    98 Eğer Rnd ()< 0.5 Then

    99 mFork.GrabFork (Ben)

    100 Diğer

    101 mKnife.GrabKnife (Ben)

    102 Eğer Son

    103 Döngü

    104 MsgBox (Ben.Adım & "yiyebilir!")

    105 mBıçak = Yeni Bıçak ()

    106 mFork = Yeni Çatal ()

    107 Bitiş Alt

    108 Son Sınıf

    Ana prosedür Main (satır 4-16), Programmer sınıfının iki örneğini oluşturur ve ardından aşağıda açıklanan Programmer sınıfının (satır 95-108) kritik Eat yöntemini yürütmek için iki iş parçacığı başlatır. Ana prosedür, iş parçacıklarının adlarını belirler ve bunları ayarlar; muhtemelen olan her şey anlaşılabilir ve yorumsuzdur.

    Fork sınıfının kodu daha ilginç görünüyor (17-38. satırlar) (benzer bir Bıçak sınıfı 39-60. satırlarda tanımlanmıştır). 18 ve 19 numaralı satırlar, fişin şu anda mevcut olup olmadığını ve değilse kimin kullandığını öğrenebileceğiniz ortak alanların değerlerini belirtir. ReadOnly özelliği OwnUtensi1 (20-24 satırlar), en basit bilgi aktarımı için tasarlanmıştır. Fork sınıfının merkezinde, 25-27. satırlarda tanımlanan GrabFork “grab the fork” yöntemi bulunur.

    1. 26 ve 27. satırlar hata ayıklama bilgilerini konsola yazdırır. Yöntemin ana kodunda (28-36. satırlar), çatala erişim nesne ile senkronize edilir.kemer beni Programımız yalnızca bir çatal kullandığından, sync over Me, iki iş parçacığının aynı anda onu yakalayamamasını sağlar. Slee "p komutu (34. satırda başlayan blokta), çatal/bıçak alma ile yemek başlatma arasındaki gecikmeyi simüle eder. Uyku komutunun nesnelerin kilidini açmadığını ve yalnızca kilitlenmeleri hızlandırdığını unutmayın!
      Ancak, en çok ilgiyi çeken Programmer sınıfının kodudur (61-108. satırlar). 67-70 satırları, programda yalnızca bir çatal ve bıçağın olmasını sağlamak için genel bir kurucu tanımlar. Özellik kodu (74-94. satırlar) basittir ve yorum gerektirmez. En önemli şey, iki ayrı iş parçacığı tarafından yürütülen Eat yönteminde gerçekleşir. İşlem, bir akış bıçakla birlikte çatalı yakalayana kadar bir döngüde devam eder. 98-102 satırlarında nesne, kilitlenmeye neden olan Rnd çağrısını kullanarak çatalı/bıçağı rastgele yakalar. Aşağıdakiler olur:
      Tot'un Eat yöntemini çalıştıran iş parçacığı çağrılır ve döngüye girer. Bıçağı alır ve bekleme durumuna geçer.
    2. Bob's Eat yöntemini çalıştıran iş parçacığı çağırır ve döngüye girer. Bıçağı tutamaz, ancak çatalı tutar ve bekleme durumuna geçer.
    3. Tot'un Eat yöntemini çalıştıran iş parçacığı çağrılır ve döngüye girer. Çatalı almaya çalışır, ancak çatal zaten Bob tarafından tutulmuştur; iş parçacığı bekleme durumuna geçer.
    4. Bob's Eat yöntemini çalıştıran iş parçacığı çağırır ve döngüye girer. Bıçağı yakalamaya çalışır, ancak bıçak zaten Thoth tarafından ele geçirilmiştir; iş parçacığı bekleme durumuna geçer.

    Bütün bunlar süresiz devam ediyor - tipik bir kilitlenme durumuyla karşı karşıyayız (programı çalıştırmayı deneyin ve kimsenin bu şekilde yiyemeyeceğini göreceksiniz).
    Ayrıca thread penceresinde bir kilitlenme olup olmadığını da görebilirsiniz. Programı çalıştırın ve Ctrl + Break tuşları ile durdurun. Me değişkenini görünüm alanına dahil edin ve akışlar penceresini açın. Sonuç, Şekil 2'de gösterilene benziyor. 10.7. Şekilden, Bob'un ipliğinin bir bıçağı tuttuğunu ancak çatalının olmadığını görebilirsiniz. Tot satırındaki İplikler penceresine sağ tıklayın ve içerik menüsünden İpliğe Geç'i seçin. Görüntü alanı, Thoth akışının bir çatalı olduğunu ancak bıçağı olmadığını gösteriyor. Tabii ki, bu yüzde yüz kanıt değil, ancak bu tür davranışlar en azından bir şeylerin yanlış olduğundan şüpheleniyor.
    Bir nesne ile senkronizasyon seçeneği (evdeki -sıcaklığın arttığı programda olduğu gibi) mümkün değilse, karşılıklı kilitlenmeleri önlemek için, senkronizasyon nesnelerini numaralandırabilir ve her zaman sabit bir sırayla yakalayabilirsiniz. Yemek programcısı benzetmesine devam edelim: iplik her zaman önce bıçağı sonra çatalı alırsa, kilitlenme ile ilgili herhangi bir sorun olmayacaktır. Bıçağı tutan ilk dere normal şekilde yemek yiyebilecektir. Program akışlarının diline çevrildiğinde, bu, nesne 2'nin yakalanmasının yalnızca nesne 1'in ilk olarak yakalanması durumunda mümkün olduğu anlamına gelir.

    Pirinç. 10.7. İş parçacığı penceresindeki kilitlenmelerin analizi

    Bu nedenle, 98. satırdaki Rnd çağrısını kaldırır ve onu snippet ile değiştirirsek

    mFork.GrabFork (Ben)

    mKnife.GrabKnife (Ben)

    kilitlenme ortadan kalkar!

    Veriler oluşturuldukça işbirliği yapın

    Çok iş parçacıklı uygulamalarda, iş parçacıklarının yalnızca paylaşılan verilerle çalışmakla kalmayıp, aynı zamanda görünmesini de beklediği bir durum vardır (yani, iş parçacığı 2'nin kullanabilmesi için önce iş parçacığı 1'in veri oluşturması gerekir). Veriler paylaşıldığından, verilere erişimin senkronize edilmesi gerekir. Hazır verilerin görünümü hakkında bekleyen iş parçacıklarının bildirilmesi için araçlar sağlamak da gereklidir.

    Bu duruma genellikle denir tedarikçi/tüketici sorunu.İş parçacığı henüz var olmayan verilere erişmeye çalışıyor, bu nedenle kontrolü gerekli verileri oluşturan başka bir iş parçacığına aktarması gerekiyor. Sorun aşağıdaki kodla çözüldü:

    • İş parçacığı 1 (tüketici) uyanır, senkronize bir yöntem girer, veri arar, bulamaz ve bekleme durumuna geçer. ön olarakfiziksel olarak, besleme ipliğinin çalışmasına müdahale etmemek için engellemeyi kaldırmalıdır.
    • İş parçacığı 2 (sağlayıcı), iş parçacığı 1 tarafından serbest bırakılan senkronize bir yönteme girer, yaratır akış 1 için veri ve bir şekilde akış 1'i verilerin varlığı hakkında bilgilendirir. Ardından, iş parçacığı 1'in yeni verileri işleyebilmesi için kilidi serbest bırakır.

    Bu sorunu sürekli olarak thread 1'i çağırarak ve değeri > thread 2 tarafından ayarlanan koşul değişkeninin durumunu kontrol ederek çözmeye çalışmayın. sebepsiz yere çağrılmak; ve iş parçacığı 2 o kadar sık ​​bekleyecek ki veri oluşturmak için zaman tükenecek.

    Sağlayıcı/tüketici ilişkileri çok yaygındır, bu nedenle çok iş parçacıklı programlama sınıfı kitaplıklarında bu tür durumlar için özel ilkeller oluşturulur. NET'te bu temel öğeler Wait ve Pulse-PulseAl 1 olarak adlandırılır ve Monitor sınıfının bir parçasıdır. Şekil 10.8 programlamak üzere olduğumuz durumu göstermektedir. Program üç iş parçacığı kuyruğu düzenler: bir bekleme kuyruğu, bir engelleme kuyruğu ve bir yürütme kuyruğu. İş parçacığı zamanlayıcı, bekleme kuyruğunda olan iş parçacıklarına CPU zamanı ayırmaz. Bir iş parçacığının zaman ayırması için yürütme kuyruğuna taşınması gerekir. Sonuç olarak, uygulamanın çalışması, bir koşullu değişkeni yoklarken olduğundan çok daha verimli bir şekilde organize edilir.

    Sözde kodda, veri tüketici deyimi şu şekilde formüle edilir:

    "Aşağıdaki türde bir senkronize bloğa giriş

    Veri yokken

    Bekleme kuyruğuna git

    Döngü

    Veri varsa, işleyin.

    Senkronize bloktan ayrıl

    Bekle komutu yürütüldükten hemen sonra iş parçacığı askıya alınır, kilit serbest bırakılır ve iş parçacığı bekleyen kuyruğa girer. Kilit serbest bırakıldığında, yürütme kuyruğundaki iş parçacığının çalışmasına izin verilir. Zamanla, bir veya daha fazla engellenen iş parçacığı, bekleyen kuyrukta olan iş parçacığının çalışması için gerekli verileri oluşturacaktır. Veri doğrulama bir döngü içinde yapıldığından, veri kullanımına geçiş (döngüden sonra) yalnızca veri işlemeye hazır olduğunda gerçekleşir.

    Sözde kodda, veri sağlayıcı deyimi şöyle görünür:

    "Senkronize bir görünüm bloğu girme

    Veri gerekli DEĞİLDİR

    Bekleme kuyruğuna git

    Aksi takdirde Veri Üretin

    Veriler hazır olduğunda Pulse-PulseAll'ı arayın.

    bir veya daha fazla iş parçacığını engelleme kuyruğundan yürütme kuyruğuna taşımak için. Senkronize bir blok bırakın (ve çalıştırma kuyruğuna dönün)

    Programımızın, bir ebeveyni para kazanan ve bu parayı harcayan bir çocuğa sahip bir aileyi simüle ettiğini varsayalım. para bittiğindeÇocuğun yeni bir miktarın gelmesini beklemesi gerektiği ortaya çıktı. Bu modelin yazılım uygulaması şöyle görünür:

    1 Seçenek Kesin Açık

    2 İthalat System.Threading

    3 Modül Modülü

    4 Alt Ana ()

    5 Yeni Aile Olarak Aileyi Karartın ()

    6 theFamily.StartltsLife ()

    7 Bitiş Alt

    8 Son fjodül

    9

    10 Kamu Sınıfı Aile

    Tamsayı Olarak 11 Özel mMoney

    12 Özel mWeek Tamsayı = 1

    13 Public Sub StartltsLife ()

    14 Konuyu Karart Yeni Konu olarak BaşlaStarUAddressOf Me.Produce)

    15 Dim bThreadNew ThreadStarUAddressOf Me.Consume)

    16 Konuyu Yeni Konu Olarak Karartın (aThreadStart)

    17 Dim bThread As New Thread (bThreadStart)

    18 aThread.Name = "Üret"

    19 aThread.Start ()

    20 bThread.Name = "Tüket"

    21 bİplik. Başlangıç ​​()

    22 Son Alt

    23 Kamu Malı Haftada () Tamsayı Olarak

    24 Al

    25 Haftalık dönüş

    26 Son Al

    27 Set (ByVal Değeri Tam Sayı)

    28 hafta - Değer

    29 Bitiş Seti

    30 Son Özellik

    31 Kamu Malı OurMoney () Tamsayı Olarak

    32 Al

    33 mPara İadesi

    34 Son Al

    35 Set (ByVal Değeri Tam Sayı Olarak)

    36 mPara = Değer

    37 Uç Seti

    38 Son Özellik

    39 Kamu Alt Ürünleri ()

    40 İplik.Uyku (500)

    41 Yap

    42 Monitör.Gir (Ben)

    43 Ben Yaparken.Paramız> 0

    44 Monitör.Bekle (Ben)

    45 Döngü

    46 Ben.Paramız = 1000

    47 Monitor.PulseAll (Ben)

    48 Monitor.Exit (Ben)

    49 döngü

    50 Bitiş Alt

    51 Genel Alt Tüketim ()

    52 MsgBox ("Tüketim iş parçacığındayım")

    53 Yap

    54 Monitor.Enter (Ben)

    55 Ben Yaparken.Paramız = 0

    56 Monitor.Bekle (Ben)

    57 döngü

    58 Console.WriteLine ("Sevgili ebeveynim, senin hepsini harcadım" & _

    haftadaki para "& TheWeek)

    59 Hafta + = 1

    60 TheWeek = 21 * 52 ise System.Environment.Exit (0)

    61 Ben.Paramız = 0

    62 Monitor.PulseAll (Ben)

    63 Monitör.Çık (Ben)

    64 döngü

    65 Son Alt

    66 Son Sınıf

    StartltsLife yöntemi (satır 13-22), Üret ve Tüket akışlarını başlatmaya hazırlanır. En önemli şey, Produce akışlarında (39-50 satırlar) ve Tüketim'de (51-65 satırlar) gerçekleşir. Alt Üretim prosedürü para olup olmadığını kontrol eder ve para varsa bekleme kuyruğuna gider. Aksi takdirde, ebeveyn para üretir (satır 46) ve durumdaki bir değişiklik hakkında bekleme kuyruğundaki nesneleri bilgilendirir. Pulse-Pulse All çağrısının, yalnızca Monitor.Exit komutuyla kilit serbest bırakıldığında etkili olduğunu unutmayın. Tersine, Alt Tüketim prosedürü paranın kullanılabilirliğini kontrol eder ve para yoksa, bekleyen ebeveyni bu konuda bilgilendirir. 60. satır, programı 21 koşullu yıl sonra sonlandırıyor; çağrı sistemi. Environment.Exit (0), End komutunun .NET analogudur (End komutu da desteklenir, ancak System. Environment. Exit'ten farklı olarak, işletim sistemine bir çıkış kodu döndürmez).

    Bekleme kuyruğuna alınan iş parçacıkları, programınızın diğer bölümleri tarafından serbest bırakılmalıdır. Bu nedenle PulseAll yerine Pulse kullanmayı tercih ediyoruz. Pulse 1 çağrıldığında hangi thread'in aktif olacağı önceden bilinmediği için, kuyrukta nispeten az thread varsa, PulseAll'ı da çağırabilirsiniz.

    Grafik programlarında çoklu kullanım

    GUI uygulamalarında çoklu kullanımla ilgili tartışmamız, GUI uygulamalarında çoklu kullanımın ne için olduğunu açıklayan bir örnekle başlar. Şekil 2'de gösterildiği gibi Başlat (btnStart) ve İptal (btnCancel) düğmeleriyle bir form oluşturun. 10.9. Başlat düğmesine tıklamak, 10 milyon karakterlik rastgele bir dize içeren bir sınıf ve bu uzun dizedeki "E" harfinin oluşumlarını saymak için bir yöntem oluşturur. Uzun dizelerin daha verimli oluşturulması için StringBuilder sınıfının kullanımına dikkat edin.

    Aşama 1

    İş parçacığı 1, bunun için veri olmadığını fark eder. Wait'i çağırır, kilidi serbest bırakır ve bekleme kuyruğuna gider.



    Adım 2

    Kilit serbest bırakıldığında, iş parçacığı 2 veya iş parçacığı 3, blok kuyruğundan çıkar ve senkronize bir bloğa girerek kilidi alır.

    Aşama 3

    Diyelim ki iş parçacığı 3 senkronize bir bloğa giriyor, veri oluşturuyor ve Pulse-Pulse All'ı çağırıyor.

    Bloktan çıkıp kilidi serbest bıraktıktan hemen sonra, iş parçacığı 1 yürütme kuyruğuna taşınır. 3. iş parçacığı Pluse'ı çağırırsa, yürütme kuyruğuna yalnızca biri gireriş parçacığı, Pluse All çağrıldığında, tüm iş parçacıkları yürütme kuyruğuna gider.



    Pirinç. 10.8. Sağlayıcı/tüketici sorunu

    Pirinç. 10.9. Basit bir GUI uygulamasında çoklu kullanım

    System.Text'i içe aktarır

    Genel Sınıf Rastgele Karakterler

    StringBuilder Olarak Özel m_Data

    Özel mjength, m_count Tamsayı Olarak

    Public Sub Yeni (ByVal n As Integer)

    m_Uzunluk = n -1

    m_Data = Yeni StringBuilder (m_length) MakeString ()

    Alt Alt

    Özel Alt MakeString ()

    Dim i Tamsayı Olarak

    Dim myRnd As New Random ()

    i = 0 için m_uzunluk

    "65 ile 90 arasında rastgele bir sayı üret,

    "büyük harfe çevir

    "ve StringBuilder nesnesine ekleyin

    m_Data.Append (Chr (myRnd.Next (65.90)))

    Sonraki

    Alt Alt

    Public Sub StartCount ()

    GetE'ler ()

    Alt Alt

    Özel Alt GetEes ()

    Dim i Tamsayı Olarak

    i = 0 için m_uzunluk

    Eğer m_Data.Chars (i) = CChar ("E") ise

    m_count + = 1

    Sonraki ise Bitir

    m_CountDone = Doğru

    Alt Alt

    Herkese Açık Salt Okunur

    Özellik GetCount () As Integer Get

    Değilse (m_CountDone) O zaman

    m_count döndür

    Bitir

    End Get End Mülkiyet

    Herkese Açık Salt Okunur

    Özellik IsDone () As Boolean Get

    Dönüş

    m_CountBitti

    Bitir

    Mülkü Sonlandır

    Sınıfı Bitir

    Formdaki iki butonla ilişkili çok basit bir kod var. btn-Start_Click prosedürü, 10 milyon karakterlik bir dizeyi içine alan yukarıdaki RandomCharacters sınıfını başlatır:

    Özel Alt btnStart_Click (ByVal gönderen As System.Object.

    ByVal e As System.EventArgs) btnStart.Click'i İşler

    Yeni Rastgele Karakterler Olarak Dim RC (10000000)

    RC.StartCount ()

    MsgBox ("Es sayısı" & RC.GetCount)

    Alt Alt

    İptal düğmesi bir mesaj kutusu görüntüler:

    Özel Alt btnCancel_Click (ByVal gönderici As System.Object._

    ByVal e As System.EventArgs) btnCancel.Click'i işler

    MsgBox ("Sayma Kesildi!")

    Alt Alt

    Program çalıştırıldığında ve Başlat düğmesine basıldığında, sürekli döngü düğmenin aldığı olayı işlemesini engellediği için İptal düğmesinin kullanıcı girişine yanıt vermediği ortaya çıkıyor. Modern programlarda bu kabul edilemez!

    İki olası çözüm var. Önceki VB sürümlerinden iyi bilinen ilk seçenek, çoklu kullanımdan vazgeçer: DoEvents çağrısı döngüye dahil edilir. NET'te bu komut şöyle görünür:

    Application.DoEvents ()

    Örneğimizde, bu kesinlikle arzu edilen bir şey değil - kim bir programı on milyon DoEvent çağrısıyla yavaşlatmak ister ki! Bunun yerine döngüyü ayrı bir iş parçacığına tahsis ederseniz, işletim sistemi iş parçacıkları arasında geçiş yapacak ve İptal düğmesi işlevsel kalacaktır. Ayrı bir iş parçacığı ile uygulama aşağıda gösterilmiştir. İptal düğmesinin çalıştığını açıkça göstermek için tıkladığımızda programı sonlandırıyoruz.

    Sonraki adım: Sayıyı Göster düğmesi

    Diyelim ki yaratıcı hayal gücünüzü göstermeye karar verdiniz ve şekle şek. 10.9. Lütfen dikkat: Sayıyı Göster düğmesi henüz mevcut değildir.

    Pirinç. 10.10. Kilitli Düğme Formu

    Ayrı bir iş parçacığının sayımı yapması ve kullanılamayan düğmenin kilidini açması beklenir. Bu elbette yapılabilir; dahası, böyle bir görev oldukça sık ortaya çıkar. Ne yazık ki, en bariz şekilde hareket edemezsiniz - yapıcıdaki ShowCount düğmesine bir bağlantı tutarak veya hatta standart bir temsilci kullanarak ikincil iş parçacığını GUI iş parçacığına bağlayın. Diğer bir deyişle, asla aşağıdaki seçeneği kullanmayın (temel hatalı satırlar kalın yazılmıştır).

    Genel Sınıf Rastgele Karakterler

    StringBuilder Olarak Özel m_0ata

    Boole Olarak Özel m_CountDone

    Özel müzik. m_count Tamsayı Olarak

    Windows.Forms.Button Olarak Özel m_Button

    Public Sub New (ByVa1 n As Integer, _

    ByVal b As Windows.Forms.Button)

    m_uzunluk = n - 1

    m_Data = Yeni StringBuilder (mJength)

    m_Button = b MakeString()

    Alt Alt

    Özel Alt MakeString ()

    Tamsayı Olarak Dim I

    Dim myRnd As New Random ()

    I = 0 için m_uzunluk

    m_Data.Append (Chr (myRnd.Next (65.90)))

    Sonraki

    Alt Alt

    Public Sub StartCount ()

    GetE'ler ()

    Alt Alt

    Özel Alt GetEes ()

    Tamsayı Olarak Dim I

    I için = 0 için mjength

    Eğer m_Data.Chars (I) = CChar ("E") ise

    m_count + = 1

    Sonraki ise Bitir

    m_CountDone = Doğru

    m_Button.Enabled = Doğru

    Alt Alt

    Herkese Açık Salt Okunur

    Özellik GetCount () As Integer

    Elde etmek

    Değilse (m_CountDone) O zaman

    Yeni İstisna Atın ("Sayım henüz yapılmadı") Aksi takdirde

    m_count döndür

    Bitir

    Bitir

    Mülkü Sonlandır

    Genel Salt Okunur Özellik Boole Olarak Bitti ()

    Elde etmek

    m_CountDone döndür

    Bitir

    Mülkü Sonlandır

    Sınıfı Bitir

    Bu kodun bazı durumlarda çalışması muhtemeldir. Yine de:

    • İkincil iş parçacığının GUI'yi oluşturan iş parçacığıyla etkileşimi organize edilemez bariz anlamına geliyor.
    • Hiçbir zaman grafik programlarındaki öğeleri diğer program akışlarından değiştirmeyin. Tüm değişiklikler yalnızca GUI'yi oluşturan iş parçacığında gerçekleşmelidir.

    Bu kuralları çiğnerseniz, biz garanti ediyoruzçok iş parçacıklı grafik programlarınızda bu ince, ince hatalar oluşacaktır.

    Olayları kullanarak nesnelerin etkileşimini organize etmede de başarısız olacaktır. 06-event işçisi, RaiseEvent'in çağrıldığı aynı iş parçacığı üzerinde çalışır, bu nedenle olaylar size yardımcı olmaz.

    Yine de sağduyu, grafik uygulamaların başka bir iş parçacığından öğeleri değiştirmek için bir araç sağlaması gerektiğini belirtir. NET Framework'te, başka bir iş parçacığından GUI uygulamalarının yöntemlerini çağırmanın iş parçacığı açısından güvenli bir yolu vardır. Bu amaç için System.Windows ad alanından özel bir Method Invoker temsilcisi türü kullanılır. Formlar. Aşağıdaki kod parçası, GetEes yönteminin yeni bir sürümünü gösterir (değişen satırlar kalın harflerle gösterilmiştir):

    Özel Alt GetEes ()

    Tamsayı Olarak Dim I

    I = 0 için m_uzunluk

    Eğer m_Data.Chars (I) = CChar ("E") ise

    m_count + = 1

    Sonraki ise Bitir

    m_CountDone = Gerçek Deneme

    Dim mylnvoker As New Methodlnvoker (UpDateButton Adresi)

    myInvoker.Invoke () Catch e As ThreadlnterruptedException

    "Arıza

    Denemeyi Bitir

    Alt Alt

    Genel Alt GüncellemeDüğmesi ()

    m_Button.Enabled = Doğru

    Alt Alt

    Düğmeye yapılan iş parçacığı aramaları doğrudan değil, Yöntem Çağırıcı aracılığıyla yapılır. .NET Framework, bu seçeneğin iş parçacığı için güvenli olduğunu garanti eder.

    Çok iş parçacıklı programlamada neden bu kadar çok sorun var?

    Artık çoklu iş parçacığı ve bununla ilişkili olası sorunlar hakkında biraz bilgi sahibi olduğunuza göre, bu bölümün sonundaki bu alt bölümün başlığındaki soruyu yanıtlamanın uygun olacağına karar verdik.

    Bunun nedenlerinden biri, çoklu iş parçacığının doğrusal olmayan bir süreç olması ve bizim bir doğrusal programlama modeline alışmış olmamızdır. İlk başta, programın yürütülmesinin rastgele kesilebileceği ve kontrolün başka bir koda aktarılacağı fikrine alışmak zordur.

    Ancak, daha temel bir neden daha var: bu günlerde programcılar çok nadiren assembler'da programlıyorlar veya en azından derleyicinin demonte çıktısına bakıyorlar. Aksi takdirde, düzinelerce montaj talimatının üst düzey bir dilin (VB .NET gibi) bir komutuna karşılık gelebileceği fikrine alışmaları çok daha kolay olurdu. İş parçacığı, bu talimatların herhangi birinden sonra ve dolayısıyla üst düzey bir komutun ortasında kesilebilir.

    Ancak hepsi bu kadar değil: modern derleyiciler program performansını optimize eder ve bilgisayar donanımı bellek yönetimine müdahale edebilir. Sonuç olarak, derleyici veya donanım sizin bilginiz olmadan programın kaynak kodunda belirtilen komutların sırasını değiştirebilir [ Birçok derleyici, i = 0 ila n: b (i) = a (i): ncxt gibi dizilerin döngüsel kopyalanmasını optimize eder. Derleyici (hatta özel bir bellek yöneticisi) basitçe bir dizi oluşturabilir ve ardından tek tek öğeleri birçok kez kopyalamak yerine onu tek bir kopyalama işlemiyle doldurabilir!].

    Umarız bu açıklamalar çok iş parçacıklı programlamanın neden bu kadar çok soruna neden olduğunu daha iyi anlamanıza yardımcı olur - ya da en azından çok iş parçacıklı programlarınızın garip davranışına daha az şaşırırsınız!

    Yeni başlayanlar için en çok soru ve zorluk yaratan konu nedir? Bunu öğretmenime ve Java programcısı Alexander Pryakhin'e sorduğumda hemen yanıtladı: "Multithreading". Bu makalenin hazırlanmasında fikir ve yardım için ona teşekkürler!

    Uygulamanın iç dünyasına ve süreçlerine bakacağız, çoklu iş parçacığının özünün ne olduğunu, ne zaman yararlı olduğunu ve nasıl uygulanacağını anlayacağız - örnek olarak Java kullanarak. Farklı bir OOP dili öğreniyorsanız endişelenmeyin: temel ilkeler aynıdır.

    Akışlar ve kökenleri hakkında

    Çoklu iş parçacığını anlamak için, önce bir sürecin ne olduğunu anlayalım. İşlem, işletim sisteminin bir programı çalıştırmak için ayırdığı bir sanal bellek ve kaynak parçasıdır. Aynı uygulamanın birkaç örneğini açarsanız, sistem her biri için bir işlem tahsis edecektir. Modern tarayıcılarda, her sekmeden ayrı bir işlem sorumlu olabilir.

    Muhtemelen Windows "Görev Yöneticisi" ile karşılaşmışsınızdır (Linux'ta "Sistem Monitörü"dür) ve gereksiz çalışan işlemlerin sistemi yüklediğini ve bunların en "ağır"larının genellikle donduğunu, bu nedenle zorla sonlandırılmaları gerektiğini biliyorsunuzdur. .

    Ancak kullanıcılar çoklu görev yapmayı severler: onlara ekmek yedirmeyin - sadece bir düzine pencere açın ve ileri geri zıplayın. Bir ikilem var: Uygulamaların eşzamanlı çalışmasını sağlamanız ve aynı zamanda yavaşlamaması için sistem üzerindeki yükü azaltmanız gerekiyor. Diyelim ki donanım, sahiplerinin ihtiyaçlarına ayak uyduramıyor - sorunu yazılım düzeyinde çözmeniz gerekiyor.

    İşlemcinin daha fazla talimat yürütmesini ve birim zaman başına daha fazla veri işlemesini istiyoruz. Yani, her zaman dilimine yürütülen kodun daha fazlasını sığdırmamız gerekiyor. Bir kod yürütme birimini bir nesne olarak düşünün - bu bir iş parçacığıdır.

    Birkaç basit duruma bölerseniz, karmaşık bir duruma yaklaşmak daha kolaydır. Bu nedenle, bellekle çalışırken: "ağır" bir süreç, daha az kaynak tüketen ve kodu hesap makinesine gönderme olasılığı daha yüksek olan iş parçacıklarına bölünür (tam olarak - aşağıya bakın).

    Her uygulamanın en az bir işlemi vardır ve her işlemin ana iş parçacığı adı verilen ve gerekirse yenilerinin başlatıldığı en az bir iş parçacığı vardır.

    Konular ve süreçler arasındaki fark

      İş parçacıkları, işlem için ayrılan belleği kullanır ve işlemler kendi bellek alanlarına ihtiyaç duyar. Bu nedenle, iş parçacıkları daha hızlı oluşturulur ve tamamlanır: sistemin onlara her seferinde yeni bir adres alanı tahsis etmesine ve ardından serbest bırakmasına gerek yoktur.

      İşlemlerin her biri kendi verileriyle çalışır - yalnızca işlemler arası iletişim mekanizması aracılığıyla bir şeyler değiş tokuş edebilirler. Konular birbirlerinin verilerine ve kaynaklarına doğrudan erişir: değiştirilenler anında herkes tarafından kullanılabilir. İplik, süreçteki "arkadaşını" kontrol edebilirken, süreç yalnızca "kızlarını" kontrol eder. Bu nedenle, akışlar arasında geçiş daha hızlıdır ve aralarındaki iletişim daha kolaydır.

    Bundan sonuç nedir? Büyük miktarda veriyi olabildiğince çabuk işlemeniz gerekiyorsa, bunları ayrı iş parçacıkları tarafından işlenebilecek parçalara ayırın ve ardından sonucu bir araya getirin. Kaynağa aç süreçler oluşturmaktan daha iyidir.

    Ancak Firefox gibi popüler bir uygulama neden birden çok işlem oluşturma yoluna gidiyor? İzole sekmelerin çalışması tarayıcı için olduğundan güvenilir ve esnektir. Bir işlemde bir sorun varsa, tüm programı sonlandırmak gerekli değildir - verilerin en azından bir kısmını kaydetmek mümkündür.

    çoklu kullanım nedir

    Böylece asıl meseleye geliyoruz. Çoklu iş parçacığı, uygulama sürecinin işlemci tarafından paralel olarak - bir birim zamanda - işlenen iş parçacıklarına bölünmesidir.

    Hesaplama yükü iki veya daha fazla çekirdek arasında dağıtılır, böylece arabirim ve diğer program bileşenleri birbirlerinin çalışmalarını yavaşlatmaz.

    Çok iş parçacıklı uygulamalar tek çekirdekli işlemcilerde çalıştırılabilir, ancak daha sonra iş parçacıkları sırayla yürütülür: ilki çalıştı, durumu kaydedildi - ikincisinin çalışmasına izin verildi, kaydedildi - birinciye geri döndü veya üçüncüsü başlatıldı, vesaire.

    Meşgul insanlar sadece iki eli olduğundan şikayet ederler. İşlemler ve programlar, görevi mümkün olduğunca çabuk tamamlamak için gerektiği kadar çok ele sahip olabilir.

    Bir sinyal bekleyin: çok iş parçacıklı uygulamalarda senkronizasyon

    Birkaç iş parçacığının aynı anda aynı veri alanını değiştirmeye çalıştığını hayal edin. Sonunda kimin değişiklikleri kabul edilecek ve kimlerin değişiklikleri iptal edilecek? Paylaşılan kaynaklarla karışıklığı önlemek için iş parçacıklarının eylemlerini koordine etmesi gerekir. Bunu yapmak için sinyalleri kullanarak bilgi alışverişinde bulunurlar. Her iş parçacığı diğerlerine ne yaptığını ve ne gibi değişiklikler beklendiğini söyler. Böylece kaynakların mevcut durumuyla ilgili tüm konuların verileri senkronize edilir.

    Temel Senkronizasyon Araçları

    Karşılıklı dışlama (karşılıklı dışlama, mutex olarak kısaltılır) - şu anda paylaşılan kaynaklarla çalışmasına izin verilen iş parçacığına giden bir "bayrak". Diğer iş parçacıklarının dolu bellek alanına erişimini ortadan kaldırır. Bir uygulamada birkaç muteks olabilir ve bunlar süreçler arasında paylaşılabilir. Bir yakalama var: mutex, uygulamayı her zaman işletim sistemi çekirdeğine erişmeye zorlar, bu da maliyetlidir.

    Semafor - belirli bir anda bir kaynağa erişebilecek iş parçacığı sayısını sınırlamanıza olanak tanır. Bu, darboğazların olduğu yerde kod yürütülürken işlemci üzerindeki yükü azaltacaktır. Sorun, optimum iş parçacığı sayısının kullanıcının makinesine bağlı olmasıdır.

    Etkinlik - hangi kontrolün istenen iş parçacığına aktarılacağı üzerine bir koşul tanımlarsınız. Akışlar, birbirlerinin eylemlerini geliştirmek ve mantıksal olarak sürdürmek için olay verilerini değiş tokuş eder. Biri verileri aldı, diğeri doğruluğunu kontrol etti, üçüncüsü onu sabit diske kaydetti. Etkinlikler, iptal edilme biçimlerine göre farklılık gösterir. Bir olay hakkında birkaç ileti dizisine bildirimde bulunmanız gerekiyorsa, sinyali durdurmak için iptal işlevini manuel olarak ayarlamanız gerekecektir. Yalnızca bir hedef iş parçacığı varsa, otomatik sıfırlama olayı oluşturabilirsiniz. Akışa ulaştıktan sonra sinyalin kendisini durduracaktır. Esnek akış kontrolü için olaylar sıraya alınabilir.

    Kritik Bölüm - bir döngü sayacını ve bir semaforu birleştiren daha karmaşık bir mekanizma. Sayaç, semaforun başlangıcını istenen süre için ertelemenizi sağlar. Avantajı, çekirdeğin yalnızca bölüm meşgul olduğunda ve semaforun açılması gerektiğinde etkinleştirilmesidir. İş parçacığının geri kalanı kullanıcı modunda çalışır. Ne yazık ki, bir bölüm yalnızca bir işlem içinde kullanılabilir.

    Java'da çoklu kullanım nasıl uygulanır

    Thread sınıfı, Java'da threadlerle çalışmaktan sorumludur. Bir görevi yürütmek için yeni bir iş parçacığı oluşturmak, Thread sınıfının bir örneğini oluşturmak ve onu istediğiniz kodla ilişkilendirmek anlamına gelir. Bu iki şekilde yapılabilir:

      alt sınıf İplik;

      Runnable arabirimini sınıfınıza uygulayın ve ardından sınıf örneklerini Thread yapıcısına iletin.

    Kilitlenme konusuna değinmeyecek olsak da, threadler birbirinin çalışmasını engellediğinde ve donduğunda bunu bir sonraki yazıya bırakacağız.

    Java çoklu kullanım örneği: muteksli ping pong

    Korkunç bir şey olacağını düşünüyorsanız, nefes verin. Senkronizasyon nesneleri ile çalışmayı neredeyse eğlenceli bir şekilde ele alacağız: bir muteks tarafından iki iş parçacığı atılacaktır.Fakat aslında, aynı anda yalnızca bir iş parçacığının kamuya açık verileri işleyebileceği gerçek bir uygulama göreceksiniz.

    İlk olarak, zaten bildiğimiz Thread özelliklerini miras alan bir sınıf oluşturalım ve bir kickBall yöntemi yazalım:

    Genel sınıf PingPongThread, Konuyu genişletir (PingPongThread (Dize adı) (this.setName (ad); // iş parçacığı adını geçersiz kılar) @Override public void run () (Ball ball = Ball.getBall (); while (ball.isInGame () ) (kickBall (top);)) özel geçersiz kickBall (Top top) (eğer (! ball.getSide (). equals (getName ())) (ball.kick (getName ());)))

    Şimdi topla ilgilenelim. Bizimle basit olmayacak, akılda kalıcı olacak: böylece ona kimin, hangi taraftan ve kaç kez vurduğunu anlayabilsin. Bunu yapmak için bir muteks kullanıyoruz: her bir iş parçacığının çalışması hakkında bilgi toplayacak - bu, izole edilmiş iş parçacıklarının birbirleriyle iletişim kurmasına izin verecektir. 15. vuruştan sonra ciddi şekilde yaralanmamak için topu oyundan çıkaracağız.

    Genel sınıf Ball (özel int vuruşlar = 0; özel statik Ball örneği = yeni Ball (); özel Dize tarafı = ""; özel Ball () () statik Ball getBall () (dönüş örneği;) senkronize geçersiz vuruş (String playername) (tekmeler ++; taraf = oyuncuadı; System.out.println (tekmeler + "" + taraf);) String getSide () (dönüş tarafı;) boolean isInGame () (dönüş (tekmeler)< 15); } }

    Ve şimdi iki oyuncu dizisi sahneye giriyor. Lafı fazla uzatmadan Ping ve Pong diyelim:

    Genel sınıf PingPongGame (PingPongThread player1 = yeni PingPongThread ("Ping"); PingPongThread player2 = yeni PingPongThread ("Pong"); Ball ball; PingPongGame () (top = Ball.getBall ();) void startGame () InterruptedException (player1) atar .start (); oyuncu2.start ();))

    "Tam stadyum insan - maça başlama zamanı." Toplantının açılışını resmi olarak ilan edeceğiz - uygulamanın ana sınıfında:

    Genel sınıf PingPong (genel statik geçersiz ana (String argümanları) InterruptedException'ı atar (PingPongGame oyunu = yeni PingPongGame (); game.startGame ();))

    Gördüğünüz gibi, burada öfkeli bir şey yok. Bu, şimdilik çoklu iş parçacığı kullanımına bir giriş, ancak nasıl çalıştığını zaten biliyorsunuz ve deney yapabilirsiniz - örneğin oyunun süresini vuruş sayısıyla değil, zamanla sınırlayın. Çoklu kullanım konusuna daha sonra döneceğiz - java.util.concurrent paketine, Akka kitaplığına ve geçici mekanizmaya bakacağız. Python'da çoklu iş parçacığının uygulanması hakkında da konuşalım.

    Kil Breshearlar

    Tanıtım

    Intel'in çoklu iş parçacığı uygulama yöntemleri dört ana aşamadan oluşur: analiz, tasarım ve uygulama, hata ayıklama ve performans ayarlama. Bu, sıralı koddan çok iş parçacıklı bir uygulama oluşturmak için kullanılan yaklaşımdır. Birinci, üçüncü ve dördüncü aşamalarda yazılımla çalışmak oldukça geniş bir şekilde ele alınırken, ikinci aşamanın uygulanmasına ilişkin bilgiler açıkça yetersizdir.

    Paralel algoritmalar ve paralel hesaplama üzerine birçok kitap yayınlanmıştır. Ancak, bu yayınlar esas olarak mesaj geçişini, dağıtılmış bellek sistemlerini veya bazen gerçek çok çekirdekli platformlara uygulanamayan teorik paralel hesaplama modellerini kapsar. Çok iş parçacıklı programlama konusunda ciddi olmaya hazırsanız, muhtemelen bu modeller için algoritma geliştirme konusunda bilgiye ihtiyacınız olacaktır. Tabii ki, bu modellerin kullanımı oldukça sınırlıdır, bu nedenle birçok yazılım geliştiricisi bunları pratikte uygulamak zorunda kalabilir.

    Çok iş parçacıklı uygulamaların geliştirilmesinin her şeyden önce yaratıcı bir etkinlik ve ancak o zaman bilimsel bir etkinlik olduğunu söylemek abartı olmaz. Bu makalede, eşzamanlı programlama uygulamaları tabanınızı genişletmenize ve uygulamalarınızın iş parçacığı oluşturma verimliliğini artırmanıza yardımcı olacak sekiz kolay kural hakkında bilgi edineceksiniz.

    Kural 1. Program kodunda gerçekleştirilen işlemleri birbirinden bağımsız olarak seçin

    Paralel işleme, yalnızca birbirinden bağımsız olarak gerçekleştirilen sıralı koddaki işlemler için geçerlidir. Bağımsız eylemlerin gerçek bir tek sonuca nasıl yol açtığına iyi bir örnek, bir ev inşa etmektir. Birçok uzmanlık alanından işçileri içerir: marangozlar, elektrikçiler, sıvacılar, tesisatçılar, çatı ustaları, boyacılar, duvar ustaları, bahçıvanlar, vb. Elbette bazıları işini bitirmeden çalışmaya başlayamıyor (örneğin, çatı ustaları duvarlar yapılmadan işe başlamaz, sıva yapılmamışsa boyacılar bu duvarları boyamaz). Ama genel olarak inşaatta yer alan tüm kişilerin birbirinden bağımsız hareket ettiğini söyleyebiliriz.

    Başka bir örnek düşünün - belirli filmler için sipariş alan bir DVD kiralama mağazasının çalışma döngüsü. Depoda bu filmleri arayan nokta çalışanları arasında siparişler dağıtılır. Doğal olarak, işçilerden biri, Audrey Hepburn ile bir filmin kaydedildiği depodan bir disk alırsa, bu, Arnold Schwarzenegger ile başka bir aksiyon filmi arayan başka bir işçiyi hiçbir şekilde etkilemeyecek ve daha da az etkilenecek olan meslektaşını etkileyecektir. "Arkadaşlar" dizisinin yeni sezonu ile disk arayışı içinde. Örneğimizde, stokta film eksikliği ile ilgili tüm sorunların, siparişler kiralama noktasına ulaşmadan önce çözüldüğüne ve herhangi bir siparişin paketlenmesi ve nakliyesinin diğerlerinin işlenmesini etkilemeyeceğine inanıyoruz.

    Çalışmanızda, döngünün farklı yinelemeleri veya adımları birbirine bağlı olduğundan ve kesin bir sırayla gerçekleştirilmesi gerektiğinden, paralel olarak değil, yalnızca belirli bir sırayla işlenebilen hesaplamalarla karşılaşacaksınız. Doğadan canlı bir örnek alalım. Hamile bir geyik düşünün. Bir fetüs doğurmak ortalama sekiz ay sürdüğü için, ne derse desin, sekiz ren geyiği aynı anda hamile kalsa bile bir ay içinde bir geyik ortaya çıkmaz. Ancak, aynı anda sekiz ren geyiği, Noel Baba'nın kızağında hepsine koşulduğunda işlerini mükemmel bir şekilde yapacaktı.

    Kural 2. Paralelliği düşük düzeyde ayrıntıyla uygulayın

    Sıralı program kodunun paralel bölümlenmesine yönelik iki yaklaşım vardır: aşağıdan yukarıya ve yukarıdan aşağıya. İlk olarak, kod analizi aşamasında, program yürütme süresinin önemli bir bölümünü kaplayan kod bölümleri ("sıcak" noktalar olarak adlandırılır) belirlenir. Bu kod segmentlerini paralel olarak ayırmak (mümkünse) maksimum performans kazancı sağlayacaktır.

    Aşağıdan yukarıya yaklaşım, kod etkin noktalarının çok iş parçacıklı işlenmesini uygular. Bulunan noktaların paralel bölünmesi mümkün değilse, paralel bölme için uygun olan ve tamamlanması uzun zaman alan diğer segmentleri belirlemek için uygulama çağrı yığınını incelemelisiniz. Diyelim ki grafikleri sıkıştırmak için bir uygulama üzerinde çalışıyorsunuz. Sıkıştırma, görüntünün tek tek bölümlerini işleyen birkaç bağımsız paralel akış kullanılarak uygulanabilir. Bununla birlikte, çok iş parçacıklı "sıcak noktalar" uygulamayı başarmış olsanız bile, çağrı yığınının analizini ihmal etmeyin, bunun sonucunda program kodunun daha yüksek bir düzeyinde paralel bölme için uygun segmentler bulabilirsiniz. Bu şekilde paralel işlemenin ayrıntı düzeyini artırabilirsiniz.

    Yukarıdan aşağıya yaklaşımda, program kodunun çalışması analiz edilir ve yürütülmesi tüm görevin tamamlanmasına yol açan bireysel bölümleri vurgulanır. Ana kod bölümlerinin net bir bağımsızlığı yoksa, bağımsız hesaplamaları bulmak için bileşenlerini analiz edin. Program kodunu analiz ederek en çok CPU zamanını tüketen kod modüllerini belirleyebilirsiniz. Bir video kodlama uygulamasında iş parçacığı oluşturmanın nasıl uygulanacağına bakalım. Paralel işleme, diğer gruplardan bağımsız olarak işlenebilen kare grupları için en düşük düzeyde - bir karenin bağımsız pikselleri için veya daha yüksek bir düzeyde - uygulanabilir. Aynı anda birden fazla video dosyasını işlemek için bir uygulama yazılıyorsa, bu düzeyde paralel bölme daha da kolay olabilir ve ayrıntı en düşük olacaktır.

    Paralel hesaplamanın ayrıntı düzeyi, iş parçacıkları arasında senkronizasyondan önce gerçekleştirilmesi gereken hesaplama miktarını ifade eder. Başka bir deyişle, senkronizasyon ne kadar az sıklıkla gerçekleşirse, ayrıntı düzeyi o kadar düşük olur. İnce taneli iş parçacığı hesaplamaları, iş parçacığının sistem ek yükünün, bu iş parçacıkları tarafından gerçekleştirilen yararlı hesaplamalardan daha ağır basmasına neden olabilir. Aynı miktarda hesaplamaya sahip iş parçacığı sayısındaki artış, işleme sürecini zorlaştırır. Düşük tanecikli çoklu iş parçacığı, daha az sistem gecikmesi sağlar ve ek iş parçacıklarıyla elde edilebilecek daha fazla ölçeklenebilirlik potansiyeline sahiptir. Düşük tanecikli paralel işleme uygulamak için, yukarıdan aşağıya bir yaklaşım kullanılması ve çağrı yığınında yüksek düzeyde iş parçacığı kullanılması önerilir.

    Kural 3. Çekirdek sayısı arttıkça performansı artırmak için kodunuza ölçeklenebilirlik ekleyin.

    Çok uzun zaman önce, piyasada çift çekirdekli işlemcilere ek olarak dört çekirdekli işlemciler de ortaya çıktı. Ayrıca Intel, saniyede bir trilyon kayan nokta işlemi gerçekleştirebilen 80 çekirdekli bir işlemci duyurdu. İşlemcilerdeki çekirdek sayısı yalnızca zamanla artacağından, kodunuz ölçeklenebilirlik için yeterli potansiyele sahip olmalıdır. Ölçeklenebilirlik, bir uygulamanın sistem kaynaklarındaki artış (çekirdek sayısı, bellek boyutu, veri yolu frekansı vb.) veya veri miktarındaki artış gibi değişikliklere yeterince yanıt verme yeteneğinin yargılanabileceği bir parametredir. Gelecekteki işlemcilerde çekirdek sayısı arttıkça, sistem kaynaklarını artırarak performansı artıracak ölçeklenebilir kodlar yazın.

    C. Northecote Parkinson'un yasalarından birini açıklamak gerekirse, "veri işlemenin mevcut tüm sistem kaynaklarını kapladığını" söyleyebiliriz. Bu, bilgi işlem kaynakları arttıkça (örneğin, çekirdek sayısı) hepsinin büyük olasılıkla verileri işlemek için kullanılacağı anlamına gelir. Yukarıda tartışılan video sıkıştırma uygulamasına geri dönelim. İşlemciye ek çekirdek eklenmesinin işlenen karelerin boyutunu etkilemesi olası değildir - bunun yerine kareyi işleyen iş parçacıklarının sayısı artacak ve bu da akış başına piksel sayısında azalmaya yol açacaktır. Sonuç olarak, ek akışların organizasyonu nedeniyle, hizmet verilerinin miktarı artacak ve paralellik ayrıntı düzeyi azalacaktır. Daha olası başka bir senaryo, kodlanması gereken video dosyalarının boyutunda veya sayısında bir artıştır. Bu durumda, daha büyük (veya ek) video dosyalarını işleyecek ek akışların organizasyonu, tüm iş hacminin doğrudan artışın gerçekleştiği aşamada bölünmesine izin verecektir. Buna karşılık, bu tür yeteneklere sahip bir uygulama, ölçeklenebilirlik için yüksek bir potansiyele sahip olacaktır.

    Veri ayrıştırmayı kullanarak paralel işlemeyi tasarlamak ve uygulamak, işlevsel ayrıştırmayı kullanmaya kıyasla daha fazla ölçeklenebilirlik sağlar. Program kodundaki bağımsız işlevlerin sayısı genellikle sınırlıdır ve uygulamanın yürütülmesi sırasında değişmez. Her bağımsız işleve ayrı bir iş parçacığı (ve buna bağlı olarak bir işlemci çekirdeği) tahsis edildiğinden, çekirdek sayısındaki artışla, ek olarak organize edilen iş parçacıkları performansta bir artışa neden olmaz. Dolayısıyla, veri ayrıştırmalı paralel bölümleme modelleri, işlemci çekirdek sayısı arttıkça işlenen veri miktarının artması nedeniyle uygulamanın ölçeklenebilirliği için artan potansiyel sağlayacaktır.

    Program kodu bağımsız fonksiyonlara iş parçacığı atıyor olsa bile, giriş yükü arttığında başlatılan ek iş parçacıklarının kullanılması mümkündür. Yukarıda tartışılan ev inşa örneğine geri dönelim. Yapının kendine özgü amacı, sınırlı sayıda bağımsız görevi tamamlamaktır. Ancak, iki kat daha fazla kat inşa etmeniz talimatı verildiyse, muhtemelen bazı uzmanlık alanlarında (boyacılar, çatı ustaları, tesisatçılar, vb.) ek işçi kiralamak isteyeceksiniz. Sonuç olarak, artan iş yükünden kaynaklanan veri ayrıştırmasına uyum sağlayabilecek uygulamalar geliştirmeniz gerekiyor. Kodunuz işlevsel ayrıştırma uyguluyorsa, işlemci çekirdeği sayısı arttıkça ek iş parçacıkları düzenlemeyi düşünün.

    Kural 4. İş parçacığı güvenli kitaplıkları kullanın

    Kodunuzdaki veri etkin noktalarını işlemek için bir kitaplığa ihtiyacınız varsa, kendi kodunuz yerine kullanıma hazır işlevleri kullanmayı düşünün. Kısacası, işlevleri kütüphaneden optimize edilmiş prosedürlerde zaten sağlanan kod bölümleri geliştirerek tekerleği yeniden icat etmeye çalışmayın. Intel® Math Kernel Library (Intel® MKL) ve Intel® Integrated Performance Primitives (Intel® IPP) dahil olmak üzere birçok kitaplık, halihazırda çok çekirdekli işlemciler için optimize edilmiş çok kanallı işlevsellik içermektedir.

    Çok iş parçacıklı kitaplıklardan prosedürleri kullanırken, bir veya başka bir kitaplığı çağırmanın iş parçacıklarının normal çalışmasını etkilemeyeceğinden emin olmanız gerektiğini belirtmekte fayda var. Yani iki farklı thread'den prosedür çağrıları yapılıyorsa, her çağrıdan doğru sonuç döndürülmelidir. Prosedürler, paylaşılan kütüphane değişkenlerine atıfta bulunur ve bunları güncellerse, hesaplama sonuçlarının güvenilirliğini olumsuz yönde etkileyecek bir veri yarışı meydana gelebilir. İş parçacıklarıyla doğru şekilde çalışmak için, kitaplık prosedürü yeni olarak eklenir (yani, yerel değişkenler dışında hiçbir şeyi güncellemez) veya paylaşılan kaynaklara erişimi korumak için senkronize edilir. Sonuç: Program kodunuzda herhangi bir üçüncü taraf kitaplığı kullanmadan önce, akışlarla doğru şekilde çalıştığından emin olmak için ekli belgeleri okuyun.

    Kural 5. Uygun Bir Çoklu Kullanım Modeli Kullanın

    Çok iş parçacıklı kitaplıkların işlevlerinin, tüm uygun kod bölümlerinin paralel bölünmesi için açıkça yeterli olmadığını ve iş parçacıklarının organizasyonu hakkında düşünmeniz gerektiğini varsayalım. OpenMP kitaplığı zaten ihtiyacınız olan tüm işlevleri içeriyorsa, kendi (hantal) iş parçacığı yapınızı oluşturmak için acele etmeyin.

    Açık çoklu iş parçacığının dezavantajı, kesin iş parçacığı kontrolünün imkansızlığıdır.

    Yalnızca kaynak yoğun döngülerin paralel olarak ayrılmasına ihtiyacınız varsa veya açık iş parçacıklarının sağladığı ek esneklik sizin için ikincil ise, bu durumda fazladan iş yapmanın bir anlamı yoktur. Çoklu okumanın uygulanması ne kadar karmaşıksa, koddaki hata olasılığı o kadar yüksek ve sonraki revizyonu o kadar zor olur.

    OpenMP kitaplığı, veri ayrıştırmaya odaklanmıştır ve özellikle büyük miktarda bilgi ile çalışan döngüleri işlemek için çok uygundur. Bazı uygulamalar için yalnızca veri ayrıştırmasının geçerli olmasına rağmen, OpenMP kullanımının kabul edilemez olduğu ve açık kullanarak çoklu iş parçacığı uygulamaya devam ettiği ek gereksinimleri (örneğin, işveren veya müşteri) dikkate almak gerekir. yöntemler. Bu durumda, OpenMP, potansiyel performans kazanımlarını, ölçeklenebilirliği ve daha sonra kodu açıkça çoklu iş parçacığıyla bölmek için gerekli olacak yaklaşık çabayı tahmin etmek için ön iş parçacığı oluşturma için kullanılabilir.

    Kural 6. Program kodunun sonucu, paralel iş parçacıklarının yürütme sırasına bağlı olmamalıdır.

    Sıralı program kodu için, herhangi bir başka ifadeden sonra yürütülecek bir ifadenin tanımlanması yeterlidir. Çok iş parçacıklı kodda, iş parçacıklarının yürütme sırası tanımlanmamıştır ve işletim sistemi zamanlayıcısının talimatlarına bağlıdır. Açıkça söylemek gerekirse, bir işlemi gerçekleştirmek için başlatılacak iş parçacıklarının sırasını tahmin etmek veya daha sonra zamanlayıcı tarafından hangi iş parçacığının başlatılacağını belirlemek neredeyse imkansızdır. Tahmin, özellikle organize iş parçacıklarının sayısından daha az çekirdeğe sahip bir işlemciye sahip bir platformda çalışırken, bir uygulamanın gecikmesini azaltmak için öncelikle kullanılır. Bir iş parçacığı, önbelleğe yazılmayan bir alana erişmesi gerektiğinden veya bir G / Ç isteği yürütmesi gerektiğinden engellenirse, zamanlayıcı onu askıya alır ve başlamaya hazır olan iş parçacığını başlatır.

    Veri yarışı durumları, iş parçacığı yürütme çizelgelemesindeki belirsizliğin anlık bir sonucudur. Başka bir iş parçacığı bu değeri okumadan önce bir iş parçacığının paylaşılan bir değişkenin değerini değiştireceğini varsaymak yanlış olabilir. İyi şanslar dileriz, belirli bir platform için iş parçacıklarının yürütme sırası, uygulamanın tüm başlatmalarında aynı kalacaktır. Bununla birlikte, sistemin durumundaki en küçük değişiklikler (örneğin, verilerin sabit diskteki konumu, belleğin hızı veya hatta AC güç kaynağı ağının nominal frekansından bir sapma) farklı bir sıralamaya neden olabilir. iş parçacığı yürütme. Bu nedenle, yalnızca belirli bir iş parçacığı dizisiyle doğru şekilde çalışan program kodu için, "veri yarışı" durumları ve kilitlenmelerle ilgili sorunlar olasıdır.

    Performans kazanımı açısından, iş parçacıklarının yürütme sırasını kısıtlamamak tercih edilir. Kesin bir akış yürütme dizisine, yalnızca önceden belirlenmiş bir kriter tarafından belirlenen acil durumlarda izin verilir. Böyle bir durumda, iş parçacıkları sağlanan senkronizasyon mekanizmaları tarafından belirtilen sırayla başlatılacaktır. Örneğin, bir masanın üzerine yayılmış bir gazete okuyan iki arkadaşı hayal edin. Birincisi, farklı hızlarda okuyabilirler ve ikincisi, farklı makaleleri okuyabilirler. Ve burada gazetenin yayılmasını ilk kimin okuduğu önemli değil - her durumda, sayfayı çevirmeden önce arkadaşını beklemek zorunda kalacak. Aynı zamanda, makaleleri okuma zamanı ve sırası konusunda herhangi bir kısıtlama yoktur - arkadaşlar herhangi bir hızda okur ve sayfayı çevirirken aralarında hemen senkronizasyon gerçekleşir.

    Kural 7. Yerel akış depolamasını kullanın. Gerektiğinde belirli veri alanlarına kilitler atayın

    Senkronizasyon kaçınılmaz olarak sistem üzerindeki yükü arttırır, bu da paralel hesaplamaların sonuçlarını elde etme sürecini hiçbir şekilde hızlandırmaz, ancak doğruluğunu sağlar. Evet, senkronizasyon gereklidir, ancak aşırı kullanılmamalıdır. Senkronizasyonu en aza indirmek için, akışların yerel olarak depolanması veya tahsis edilmiş bellek alanları (örneğin, karşılık gelen akışların tanımlayıcılarıyla işaretlenmiş dizi öğeleri) kullanılır.

    Farklı iş parçacıkları tarafından geçici değişkenleri paylaşma ihtiyacı nadirdir. Bu tür değişkenler, her bir iş parçacığına yerel olarak bildirilmeli veya tahsis edilmelidir. Değerleri, iş parçacıklarının yürütülmesinin ara sonuçları olan değişkenler de ilgili iş parçacıklarına yerel olarak bildirilmelidir. Bu ara sonuçları ortak bir hafıza alanında toplamak için senkronizasyon gereklidir. Sistem üzerindeki potansiyel stresi en aza indirmek için bu ortak alanın mümkün olduğunca az güncellenmesi tercih edilir. Açık çoklu iş parçacığı yöntemleri için, çok iş parçacıklı bir kod bölümünün yürütülmesinin başlangıcından sonraki bölümün başlangıcına kadar (veya çok iş parçacıklı bir işleve yapılan bir çağrının işlenmesi sırasında) yerel verilerin bütünlüğünü sağlayan iş parçacığı yerel depolama API'leri vardır. aynı işlevin bir sonraki yürütülmesi).

    Akışları yerel olarak depolamak mümkün değilse, paylaşılan kaynaklara erişim, kilitler gibi çeşitli nesneler kullanılarak senkronize edilir. Bu durumda, kilitlerin sayısı veri bloklarının sayısına eşitse yapılması en kolay olan, belirli veri bloklarına kilitleri doğru şekilde atamak önemlidir. Belleğin birden çok alanına erişimi senkronize eden tek bir kilitleme mekanizması, yalnızca tüm bu alanlar sürekli olarak program kodunun aynı kritik bölümünde olduğunda kullanılır.

    Büyük miktarda veriye, örneğin 10.000 öğeden oluşan bir diziye erişimi senkronize etmeniz gerekiyorsa ne yapmalısınız? Tüm dizi için tek bir kilit sağlamak, uygulamada kesinlikle bir darboğazdır. Gerçekten her eleman için ayrı ayrı kilitleme düzenlemek zorunda mısınız? O zaman, verilere 32 veya 64 paralel iş parçacığı erişecek olsa bile, oldukça geniş bir bellek alanına erişim çakışmalarını önlemeniz gerekecek ve bu tür çakışmaların olasılığı %1'dir. Neyse ki, "modulo kilitleri" olarak adlandırılan bir tür altın ortalama var. N modulo kilidi kullanılırsa, her biri paylaşılan veri alanının N. kısmına erişimi senkronize eder. Örneğin, bu tür iki kilit düzenlenirse, bunlardan biri dizinin çift öğelerine, diğeri ise tek öğelere erişimi engeller. Bu durumda, gerekli elemana atıfta bulunan dişler, paritesini belirler ve uygun kilidi ayarlar. Kilit sayısı modulo, iş parçacığı sayısı ve aynı bellek alanına birkaç iş parçacığı tarafından eşzamanlı erişim olasılığı dikkate alınarak seçilir.

    Birkaç kilitleme mekanizmasının aynı anda kullanılmasının, bir bellek alanına erişimi senkronize etmesine izin verilmediğini unutmayın. Segal'in yasasını hatırlayalım: “Bir saati olan, saatin kaç olduğunu kesin olarak bilir. Birkaç saati olan insan hiçbir şeyden emin değildir." İki farklı kilidin bir değişkene erişimi kontrol ettiğini varsayalım. Bu durumda, ilk kilit kodun bir bölümü tarafından ve ikincisi başka bir bölüm tarafından kullanılabilir. Ardından, bu segmentleri yürüten iş parçacıkları, aynı anda eriştikleri paylaşılan veriler için kendilerini bir yarış durumunda bulacaktır.

    Kural 8. Çoklu iş parçacığı uygulamak için gerekirse yazılım algoritmasını değiştirin

    Hem sıralı hem de paralel uygulamaların performansını değerlendirme kriteri yürütme süresidir. Algoritmanın bir tahmini olarak asimptotik bir düzen uygundur. Bu teorik metrik, bir uygulamanın performansını değerlendirmek için neredeyse her zaman yararlıdır. Yani, diğer tüm şeyler eşit olduğunda, büyüme oranı O (n log n) (hızlı sıralama) olan bir uygulama, büyüme oranı O (n2) (seçici sıralama) olan bir uygulamadan daha hızlı performans gösterecektir. uygulamalar aynıdır.

    Asimptotik yürütme sırası ne kadar iyi olursa, paralel uygulama o kadar hızlı çalışır. Ancak, en verimli sıralı algoritma bile her zaman paralel akışlara bölünemez. Bir program etkin noktasının bölünmesi çok zorsa ve etkin nokta çağrı yığınının daha yüksek bir düzeyinde çoklu iş parçacığı okumanın bir yolu yoksa, öncelikle bölünmesi orijinal olandan daha kolay olan farklı bir sıralı algoritma kullanmayı düşünmelisiniz. Elbette, kodunuzu iş parçacığı için hazırlamanın başka yolları da var.

    Son ifadenin bir örneği olarak, iki kare matrisin çarpımını düşünün. Strassen'in algoritması en iyi asimptotik yürütme emirlerinden birine sahiptir: O (n2.81), bu sıradan üçlü iç içe döngü algoritmasının O (n3) sıralamasından çok daha iyidir. Strassen'in algoritmasına göre, her matris dört alt matrise bölünür, ardından n / 2 × n / 2 alt matrisi çarpmak için yedi özyinelemeli çağrı yapılır. Özyinelemeli çağrıları paralelleştirmek için, belirtilen boyuta ulaşana kadar yedi bağımsız alt matris çarpmasını sırayla gerçekleştirecek yeni bir iş parçacığı oluşturabilirsiniz. Bu durumda, iş parçacığı sayısı üstel olarak artacak ve alt matrislerin boyutu küçüldükçe yeni oluşturulan her bir iş parçacığı tarafından gerçekleştirilen hesaplamaların ayrıntı düzeyi artacaktır. Başka bir seçenek düşünelim - aynı anda çalışan ve bir alt matris çarpımı gerçekleştiren yedi iş parçacığından oluşan bir havuz düzenlemek. İplik havuzunun sona ermesi üzerine, alt matrisleri çarpmak için Strassen yöntemi özyinelemeli olarak çağrılır (program kodunun sıralı versiyonunda olduğu gibi). Böyle bir programı çalıştıran sistemde sekizden fazla işlemci çekirdeği varsa, bazıları boşta olacaktır.

    Matris çarpma algoritmasının iç içe üçlü bir döngü kullanarak paralelleştirilmesi çok daha kolaydır. Bu durumda, matrislerin satırlara, sütunlara veya alt matrislere bölündüğü ve iş parçacıklarının her birinin belirli hesaplamalar yaptığı veri ayrıştırma uygulanır. Böyle bir algoritmanın uygulanması, döngünün bazı seviyelerine eklenen OpenMP pragmaları kullanılarak veya matris bölümü gerçekleştiren iş parçacıklarının açıkça düzenlenmesiyle gerçekleştirilir. Bu daha basit sıralı algoritmanın uygulanması, çok iş parçacıklı Strassen algoritmasının uygulanmasına kıyasla, program kodunda çok daha az değişiklik gerektirecektir.

    Artık sıralı kodu paralele etkili bir şekilde dönüştürmek için sekiz basit kural biliyorsunuz. Bu yönergeleri izleyerek, artırılmış güvenilirlik, optimum performans ve daha az darboğaz ile çok iş parçacıklı çözümleri önemli ölçüde daha hızlı oluşturabileceksiniz.

    Çok iş parçacıklı programlama öğreticileri web sayfasına geri dönmek için şu adrese gidin: