Çok iş parçacıklı sistemler hangi amaçlarla kullanılır? Çok Yönlü Uygulamalar Geliştirmek İçin Sekiz Basit Kural

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ü") ve gereksiz çalışan işlemlerin sistemi yüklediğini ve bunların en "ağır"larının genellikle donduğunu biliyorsunuzdur, bu nedenle zorla sonlandırılmaları gerekir. .

Ancak kullanıcılar çoklu görev yapmayı severler: onlara ekmek yedirmeyin - bir düzine pencere açıp ileri geri atlamalarına izin verin. 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 daha fazla yürütülmüş kod 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 işlem, 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 her seferinde onlara yeni bir adres alanı tahsis etmesi ve ardından serbest bırakması gerekmez.

    İş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şen her şey hemen herkesin kullanımına açıktır. İ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 noktaya 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.

Tek çekirdekli işlemcilerde çok iş parçacıklı uygulamalar da ç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 uğraşırken 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, kısaltılmış - mutex) - ş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 pahalıdır.

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 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ü 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. Zamanın geri kalanında, iş parçacığı 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, bir seferde yalnızca bir iş parçacığının kamuya açık verileri işleyebileceği gerçek bir uygulama göreceksiniz.

İlk olarak, 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 oyuncuadı) (tekmeler ++; taraf = oyuncuadı; System.out.println (tekmeler + "" + taraf);) String getSide () (dönüş tarafı;) boolean isInGame () (dönüş (başlar)< 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ığı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 iş parçacığı konusuna daha sonra geri 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.

Çok iş parçacıklı programlama, olaya dayalı grafik kullanıcı arabirimleri yazmaktan veya hatta basit sıralı uygulamalar yazmaktan temel olarak 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 multithreading programlama metinlerinden derlenen, threadler ve senkronizasyon ilkelleri hakkında çok daha az önemli bilgileri uygulamaya koymaya çalışıyorlar.

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 bir 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 başvurur. Sonuç hep aynı: Aşırı karmaşık, sınırlı, kırılgan ve güvenilmez bir uygulama elde ediliyor. Kötü çok iş parçacıklı kodun doğasında var olan sürekli kilitlenme tehdidine ve diğer tehlikelere sahiptir. Açıklanamayan çökmelerden, düşük performanstan, eksik veya yanlış iş sonuçlarından bahsetmiyorum bile.

Merak etmiş olabilirsiniz: Bu neden oluyor? Yaygın bir yanılgı ş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 başarısız olur. 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ünmektedir, çü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 uyduran 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 yaygın değiller. 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östeririz.

Ortalama tek iş parçacıklı programı geliştirmek ve çok iş parçacıklı bir programa dönüştürmek konusunda zor 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 derecede uygulanabilirler. 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. Global olarak değiştirilebilir 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 var, asıl amacı şu ya da bu şekilde veri gizlemeyi uygulamak olan sayısız tasarım modeli 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şebilen 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şlamaz veya "sonuna kadar" tek, öngörülebilir bir yürütme yolu izlemez. 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 zaten zaman uyumsuz hale gelir. Olayların meydana gelme sırası çok önemli olduğu için veri bozulması olasılığı tam olarak artar. 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, veriler tanınmayacak şekilde bozulabilir.

Paralel akışlar söz konusuysa, birkaç yöntem aynı anda küresel durumda çalışabileceğinden, sorun daha da kötüleşir. Küresel devletin nasıl değiştiğini tam olarak 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 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, Heisenberg'in belirsizlik ilkesinin bildiğim en güzel örneklerinden biridir. Davranışını değiştirmeden bir programın durumunu kontrol etmek imkansızdır.

Küresel değişken 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ız, neden kodunuzdan söyleyemiyorsunuz? Programlar küresel değişkenlik durumuyla doludur ve programcılar kodun neden çalışmadığını merak ederler.

Ş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öyle 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, kilitlenmeye, dinamik kilitlenmeye, kuyrukları engellemeye ve eşzamanlılıkla ilişkili diğer tehditlere karşı da önlem almanız gerekir. Ek olarak, doğal tek engelleme sorunları vardır.
Kodu yazdığımda veya gözden geçirdiğimde, neredeyse şaşmaz bir demir kuralım var: eğer bir kilit yaptıysan, o zaman bir yerde bir hata yapmış gibisin.

Bu ifade iki şekilde yorumlanabilir:

  1. Kilitlemeye ihtiyacınız varsa, muhtemelen eşzamanlı güncellemelere karşı korumak istediğiniz global değişken durumunuz vardır. 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 kilitleme hatalarını izole 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. Ama senkronizasyon primitiflerini doğru kullanmak çok ama çok zor. Belki de bir kilidi bile doğru kullanmak için kalifiye değilsiniz. Sonuçta, kilitler ve diğer senkronizasyon ilkelleri, tüm sistem düzeyinde dikilen yapılardır. Paralel 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 temel öğeleri 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. Daha sonra yazar, bu iş parçacıklarının paralel olarak çalışabileceği çeşitli yolları düşünmeye başlayacaktır - örneğin, kilitler ve semaforlar kullanarak paylaşılan verilere erişimin kontrol edilmesi hakkında konuşun, 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 örnekler tartışılacaktır. 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. Daha sonra, herhangi bir sürüş eğitimi almadan, onu bir arabanın direksiyonuna geçirip, "Sür!" 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ığı 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 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ığı girdi kuyruğundaki kayıtları okur, işler ve sonuçları çıktı kuyruğuna koyar;
  • çıktı akışı, çıktı kuyruğundaki girdileri okur ve 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ülebilirken, 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.

Girdi, işleme ve çıktı aşamaları birbirinden izole edildiğinden, 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 çalışan 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. Benzer kütüphaneler Python, JavaScript, Ruby, PHP ve bildiğim kadarıyla diğer birçok dilde de mevcuttur. 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. Bu, 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 nasıl koordine edildiği 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 olup bittiğini 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 ileri sürülebilir. 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. Kitaplık işlevleri, paralelliğin ezici karmaşıklığını gizleme konusunda harika bir iş çıkarır. 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.

NS Bu makale, bu yılan topunu çözmenin çocuk oyuncağı olduğunu düşünen deneyimli Python terbiyecileri için değil, yeni bağımlı python için çoklu iş parçacığı yeteneklerine yüzeysel bir genel bakış.

Ne yazık ki, Python'da çoklu okuma konusunda Rusça'da çok fazla materyal yok ve örneğin GIL hakkında hiçbir şey duymamış olan pitoncılar kıskanılacak bir düzenlilikle karşıma çıkmaya başladı. Bu yazıda çok iş parçacıklı python'un en temel özelliklerini açıklamaya çalışacağım, size GIL'in ne olduğunu ve onunla (veya onsuz) nasıl yaşayacağınızı ve çok daha fazlasını anlatacağım.


Python büyüleyici bir programlama dilidir. Birçok programlama paradigmasını mükemmel bir şekilde birleştirir. Bir programcının karşılaşabileceği görevlerin çoğu burada kolay, zarif ve özlü bir şekilde çözülür. Ancak tüm bu problemler için tek iş parçacıklı bir çözüm genellikle yeterlidir ve tek iş parçacıklı programlar genellikle tahmin edilebilir ve hata ayıklaması kolaydır. Aynı şey çok iş parçacıklı ve çok işlemli programlar için söylenemez.

Çok iş parçacıklı uygulamalar


Python'un bir modülü var diş açma , ve çok iş parçacıklı programlama için ihtiyacınız olan her şeye sahiptir: çeşitli kilit türleri, bir semafor ve bir olay mekanizması vardır. Tek kelimeyle - çok iş parçacıklı programların büyük çoğunluğu için gereken her şey. Üstelik tüm bu araçları kullanmak oldukça basit. 2 thread başlatan bir program örneğini ele alalım. Bir iş parçacığı on "0" yazar, diğeri - on "1" ve kesinlikle sırayla.

içe aktarma iş parçacığı

kesin yazar

xrange (10) içindeki i için:

x yazdır

Event_for_set.set ()

# init olayları

e1 = iş parçacığı oluşturma.Olay ()

e2 = iş parçacığı oluşturma.Olay ()

# init iş parçacığı

0, e1, e2))

1, e2, e1))

# konu başlat

t1.başlangıç ​​()

t2.başlangıç ​​()

t1.birleştir ()

t2.birleştir ()


Sihir veya vudu kodu yok. Kod açık ve tutarlıdır. Ayrıca gördüğünüz gibi bir fonksiyondan akım oluşturduk. Bu, küçük görevler için çok uygundur. Bu kod da oldukça esnektir. Diyelim ki “2” yazan üçüncü bir işlemimiz var, o zaman kod şöyle görünecek:

içe aktarma iş parçacığı

kesin yazar (x, event_for_wait, event_for_set):

xrange (10) içindeki i için:

Event_for_wait.wait () # olay için bekle

Event_for_wait.clear () # gelecek için temiz olay

x yazdır

Event_for_set.set () # komşu iş parçacığı için olay ayarla

# init olayları

e1 = iş parçacığı oluşturma.Olay ()

e2 = iş parçacığı oluşturma.Olay ()

e3 = iş parçacığı oluşturma.Olay ()

# init iş parçacığı

t1 = threading.Thread (hedef = yazar, argümanlar = ( 0, e1, e2))

t2 = threading.Thread (hedef = yazar, argümanlar = ( 1, e2, e3))

t3 = threading.Thread (hedef = yazar, argümanlar = ( 2, e3, e1))

# konu başlat

t1.başlangıç ​​()

t2.başlangıç ​​()

t3.başlangıç ​​()

e1.set () # ilk olayı başlat

# konuları ana konuya birleştir

t1.birleştir ()

t2.birleştir ()

t3.birleştir ()


Yeni bir olay, yeni bir konu ekledik ve hangi parametrelerle biraz değiştirdik?
akışlar başlar (elbette, örneğin MapReduce kullanarak daha genel bir çözüm yazabilirsiniz, ancak bu, bu makalenin kapsamı dışındadır).
Gördüğünüz gibi, hala bir sihir yok. Her şey basit ve anlaşılır. Daha ileri gidelim.

Küresel Tercüman Kilidi


İş parçacığı kullanmanın en yaygın iki nedeni vardır: birincisi, modern işlemcilerin çok çekirdekli mimarisini kullanma verimliliğini ve dolayısıyla programın performansını artırmak;
ikincisi, programın mantığını paralel, tamamen veya kısmen asenkron bölümlere ayırmamız gerekiyorsa (örneğin, aynı anda birkaç sunucuya ping yapabilmek için).

İlk durumda, Global Tercüman Kilidi (veya kısaca GIL) gibi bir Python sınırlaması (veya daha doğrusu ana CPython uygulaması) ile karşı karşıyayız. GIL kavramı, bir işlemci tarafından aynı anda yalnızca bir iş parçacığının yürütülebilmesidir. Bu, ayrı değişkenler için iş parçacıkları arasında bir mücadele olmaması için yapılır. Yürütülebilir iş parçacığı tüm ortama erişim sağlar. Python'daki iş parçacığı uygulamasının bu özelliği, iş parçacıklarıyla çalışmayı büyük ölçüde basitleştirir ve belirli bir iş parçacığı güvenliği sağlar.

Ancak ince bir nokta var: Çok iş parçacıklı bir uygulama, aynı şeyi yapan tek iş parçacıklı bir uygulama ile tam olarak aynı süreyi veya CPU'daki her iş parçacığının yürütme süresinin toplamı kadar çalışacak gibi görünebilir. Ama burada hoş olmayan bir etki bizi bekliyor. Programı düşünün:

fout olarak açık ("test1.txt", "w") ile:

xrange'daki i için (1000000):

yazdır >> fout, 1


Bu program bir dosyaya sadece bir milyon satır “1” yazıyor ve bunu bilgisayarımda ~ 0.35 saniyede yapıyor.

Başka bir program düşünün:

iş parçacığı içe aktarma iş parçacığından

def yazar (dosya adı, n):

fout olarak open (dosya adı, "w") ile:

xrange (n) içindeki i için:

yazdır >> fout, 1

t1 = İş parçacığı (hedef = yazar, argümanlar = ("test2.txt", 500000,))

t2 = İş parçacığı (hedef = yazar, argümanlar = ("test3.txt", 500000,))

t1.başlangıç ​​()

t2.başlangıç ​​()

t1.birleştir ()

t2.birleştir ()


Bu program 2 iş parçacığı oluşturur. Her iş parçacığında ayrı bir dosyaya yarım milyon satır "1" yazar. Aslında, iş miktarı önceki programdakiyle aynıdır. Ancak zamanla burada ilginç bir etki elde edilir. Program 0,7 saniyeden 7 saniyeye kadar çalışabilir. Bu neden oluyor?

Bunun nedeni, bir iş parçacığının bir CPU kaynağına ihtiyacı olmadığında, GIL'i serbest bırakması ve şu anda onu ve başka bir iş parçacığını ve ayrıca ana iş parçacığını almaya çalışabilmesidir. Aynı zamanda çok sayıda çekirdek olduğunu bilen işletim sistemi, çekirdekler arasında iş parçacığı dağıtmaya çalışarak her şeyi ağırlaştırabilir.

UPD: Şu anda, Python 3.2'de, özellikle her iş parçacığının kontrolü kaybettikten sonra kısa bir süre beklemesi nedeniyle, bu sorunun kısmen çözüldüğü geliştirilmiş bir GIL uygulaması vardır. GIL'i tekrar yakalayabilir (İngilizce'de iyi bir sunum var)

“Yani Python'da verimli çok iş parçacıklı programlar yazamıyor musunuz?” Diye soruyorsunuz. Hayır, elbette, bir çıkış yolu ve hatta birkaç tane var.

Çoklu işlem uygulamaları


Bir anlamda önceki paragrafta açıklanan sorunu çözmek için Python'un bir modülü vardır. alt süreç ... Paralel bir iş parçacığında yürütmek istediğimiz bir program yazabiliriz (aslında zaten bir süreç). Ve başka bir programda bir veya daha fazla iş parçacığında çalıştırın. Bu, programımızı gerçekten hızlandıracaktır, çünkü GIL başlatıcısında oluşturulan iş parçacıkları açılmaz, yalnızca çalışan işlemin sona ermesini bekler. Ancak bu yöntemin birçok sorunu vardır. Asıl sorun, süreçler arasında veri aktarımının zorlaşmasıdır. Bir şekilde nesneleri seri hale getirmeniz, PIPE veya diğer araçlar aracılığıyla iletişim kurmanız gerekir, ancak tüm bunlar kaçınılmaz olarak ek yük getirir ve kodun anlaşılması zorlaşır.

Başka bir yaklaşım bize burada yardımcı olabilir. Python'un çok işlemli bir modülü var ... İşlevsellik açısından, bu modül benzer diş açma ... Örneğin süreçler aynı şekilde normal fonksiyonlardan oluşturulabilir. İşlemlerle çalışma yöntemleri, iş parçacığı modülündeki iş parçacıklarıyla neredeyse aynıdır. Ancak süreçlerin senkronizasyonu ve veri alışverişi için diğer araçları kullanmak gelenekseldir. Kuyruklardan (Kuyruk) ve borulardan (Boru) bahsediyoruz. Ancak, iş parçacığında olan kilitlerin, olayların ve semaforların analogları da burada.

Ek olarak, çoklu işlem modülü, paylaşılan bellekle çalışmak için bir mekanizmaya sahiptir. Bunun için modül, süreçler arasında "paylaşılabilen" bir değişken (Değer) ve bir dizi (Dizi) sınıflarına sahiptir. Paylaşılan değişkenlerle çalışmanın rahatlığı için yönetici sınıflarını kullanabilirsiniz. Daha esnektirler ve kullanımı daha kolaydır, ancak daha yavaştırlar. Unutulmamalıdır ki multiprocessing.sharedctypes modülünü kullanarak ctypes modülünden ortak tipler yapmak için güzel bir fırsat vardır.

Ayrıca çoklu işlem modülünde işlem havuzları oluşturmak için bir mekanizma vardır. Bu mekanizma, Master-Worker modelini uygulamak veya paralel bir Harita uygulamak için (bir anlamda Master-Worker'ın özel bir durumudur) kullanmak için çok uygundur.

Çoklu işlem modülüyle çalışmanın ana sorunlarından, bu modülün göreceli platform bağımlılığına dikkat etmek önemlidir. İşlemlerle çalışma farklı işletim sistemlerinde farklı düzenlendiğinden, koda bazı kısıtlamalar getirilir. Örneğin, Windows bir çatal mekanizmasına sahip değildir, bu nedenle işlem ayırma noktası şu şekilde sarılmalıdır:

eğer __name__ == "__main__":


Ancak, bu tasarım zaten iyi bir formdur.

Başka...


Python'da paralel uygulamalar yazmak için başka kütüphaneler ve yaklaşımlar da vardır. Örneğin, Hadoop + Python veya çeşitli Python MPI uygulamalarını (pyMPI, mpi4py) kullanabilirsiniz. Hatta mevcut C++ veya Fortran kitaplıklarının sarmalayıcılarını da kullanabilirsiniz. Burada Pyro, Twisted, Tornado ve diğerleri gibi çerçevelerden/kütüphanelerden bahsedilebilir. Ancak tüm bunlar zaten bu makalenin kapsamı dışındadır.

Tarzımı beğendiyseniz, bir sonraki yazıda size PLY'de basit tercümanların nasıl yazılacağını anlatmaya çalışacağım. ve ne için kullanılabilirler.

Bölüm #10.

Çok iş parçacıklı uygulamalar

Modern işletim sistemlerinde çoklu görev hafife alınır [ Apple OS X'in ortaya çıkmasından ö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 seviyesi olarak görülebilir: farklı görevler 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 bundan tam olarak faydalanmaz. Er ya da geç, bir eğitim makinesinden gerçek bir makineye geçmeniz gerekiyor 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).

Dürüstçe 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 gidiyor 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 Threading ad alanının sınıfları hakkında basılı belgeler 100 sayfadan fazla sürer. Ç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 olan yerlerde ç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ı yetenekler ortaya çıktı ve bunlar olmadan herhangi bir modern e-posta programını hayal etmek imkansız. 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 engellenmesini önlemeye yardımcı olabilir.

Çoklu iş parçacığına giriş

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ın yürütülmesine 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, bu da daha 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, örneğin e-posta alma ve yeni mesajlar hazırlamada 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ıklarının sayısı işlemcilerin 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 genellikle 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ülür.

"Nesne yürütülüyor" ifadesi ile 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 yanlış 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 dizilerinde 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 olarak sonlandırılması 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 ve prosedürler arasındaki doğrudan bağlantı, bu resme hakim olanın delegeler(bkz. bölüm 6). Özellikle, Threading ad alanı, genellikle yazılım 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 çalıştığı sanal alan" olarak tanımlanan, uygulama etki alanları adı verilen alanlarda ç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 AppDomain sınıfı, .NET sistem sınıflarında yansıma yapılırken 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 Bitiş

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 Bitiş

Sınıfı Bitir

Do döngüsü koşulu her zaman doğru olduğundan, SubtractFromCounter yordamına 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 pasaj, iş parçacığını ve Imports komutunu başlatan Sub Main prosedürünü gösterir:

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 Bitiş

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 SubtractFromCounter prosedürünün adresini satır 1'de oluşturulan test sınıfı örneğine 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öylüyoruz.

Ö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şlaması 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ığı oluşturma ilkesini kullanır. Zaman niceleme, daha önce çok iş parçacıklı programlarda ortaya çıkan yaygın sorunlardan birini de çö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 kaçırmayı ö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 istenen 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 niceleme şemaları, her bir iş parçacığına ayrılan minimum bir zaman dilimine sahip olduğundan, .NET programlamasında, CPU'ya özel yakalamalarla ilgili sorunlar o kadar ciddi değildir. Öte yandan, .NET çerçevesi başka sistemlere 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. İncir. 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 2'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 askıya alınması 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: iş parçacığı 2'nin bitiminden tam olarak ne kadar süre sonra iş parçacığı 1'in çalışmaya başlayacağını söylemek imkansız.Bir boole değeri döndüren başka bir Join sürümü var:

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, iş parçacığı 2 belirtilen zaman aşımı aralığı sona ermeden sonlandırılırsa 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)

Genellikle kodun, yürütülmesi gereken tamamen farklı bir iş parçacığında yürütüldüğü ortaya çıktı.

"Program akışlarının deterministik olmayan zamanlaması" 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 genellikle bir iş parçacığının geçerli durumu hakkında bilgi veren ThreadState özelliğini kullanır.

Akışlar penceresi

Visual Studio .NET'in Threads penceresi, çok iş parçacıklı programlarda hata 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 iş parçacığını durdurabilirsiniz (daha sonra durdurulan iş parçacığı devam ettirilebilir). Hata ayıklama sırasında, hatalı çalışan bir iş parçacığının uygulamaya müdahale etmesini önlemek için iş parçacıklarının durdurulması sıklıkla 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önteminin 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 iş parçacıkları (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ğu 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ığında çalışır, bu nedenle, programları askıya almak için Sleep yöntemi de kullanılır (Threadipg ad alanı program tarafından içe aktarılmadıysa, 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 otomatik olarak sonlandırılır, ancak bazen belirli faktörler meydana geldiğinde yöntemin (ve dolayısıyla iş parçacığının) sona ermesi 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ü dahil edilir:

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. Eğer iş parçacığının zamanından önce sona ermesini bekliyorsanız, bir döngü koşulunda yalnızca 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 pasif hale getirilmiş 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. Ancak, Yakalama bölümünün bir Kesme çağrısında iş parçacığını sonlandırması gerekmez - 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 özel durumun 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: Durdurma ve Askıya Alma ö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 çalışmayı otomatik olarak 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 ciddi 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 çıkarma programı 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şlemciye sahip 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 Bitiş

Birleştirilmiş alanı bilgi depolamak ve almak için kullanamayacağımız için (çok kanallı programların grafiksel bir arayüzle yazılması bu bölümün son bölümünde ele alınmıştı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 Bitiş

Bitiş Modülü

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 Satırı ()

Bitir Dene Bitir İşlevi

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

Dim Location 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 verilerin değişmesine 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 programa dikkatinizi çekmek istiyoruz. 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 gecikme ile 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: Evdeki sıcaklık 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ığı Oluşturma Olarak Karartın.İş parçacığı Başlangıcı

17 İpliği İplik Olarak Karartın

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 Yakalama Tae As ThreadlnterruptedException

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 belirler. 13-28 arasındaki satırlar oldukça karmaşık bir ev inşaatçısını tanımlar. 18-27. satırlar programı anlamanın anahtarıdı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 saklanı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 yükseltmeye 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 çıktısının iş parçacıklarının yürütme sırasına 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, atomik bir birim olarak her iş parçacığı tarafından kesintisiz olarak yürütülmelidir. Atomik bir blokta 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 Bitiş

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ışma hızını birkaç kat azaltır. Yalnızca en çok ihtiyaç duyulan kodu senkronize edin ve kilidi mümkün olan en kısa sürede serbest bırakın.

Temel koleksiyon sınıfları, çok iş parçacıklı uygulamalarda 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 atıldığı ç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, yalnızca aşağıdaki parçada yapıldığı gibi, yazma ve okuma için mevcut olan ortak boole özelliğine senkronize etmeniz gerekir:

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

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

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

Ö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ı

SyncLock komutunun kullanımı, yukarıdaki basit örneklerde gösterilmeyen bazı incelikleri içerir. 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, birinci ve ikinci olmak üzere iki senkronize yönteminiz var ve her iki yöntem de bigLock nesnesinde senkronize edildi. İş parçacığı 1 önce yönteme girdiğinde ve bigLock'u yakaladığı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 yaklaşık bir benzeridir:

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ı kullanan bu işlemler, SyncLock komutunu kullanmaktan çok daha hızlıdır.

birbirine kenetleme

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çacıkları bazen oldukça önemsiz hatalar meydana gelir. Ne yazık ki, çoğu durumda, iş parçacıklarının çok sık engellenmesine yol açacağından, tek bir nesne üzerinde senkronizasyona izin verilmez.

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 edilen 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 kilitlenmeyen 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 Yeni Programcı Olarak Dim Bob ("Bob")

7 Konuyu Karart Yeni Konu Başlangıcı Olarak Başla (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 Programcı)

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

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

27 Console.WriteLine (Me.OwnsUtensil & "çata sahip."). ...

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 Boole = 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 Mülk

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 Al

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 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 (satır 28-36), çatala erişim nesne ile senkronize edilir.kemer beni. Programımız yalnızca bir çatal kullandığından, Me sync, aynı anda iki iş parçacığının yakalayamamasını sağlar. Slee "p komutu (34. satırdan başlayan blokta), çatal/bıçak tutma ile yeme başlangıcı arasındaki gecikmeyi simüle eder. Sleep komutunun nesnelerin kilidini açmadığını ve yalnızca kilitlenmeleri hızlandırdığını unutmayın!
    Ancak, en ilginç olanı 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ılır ve döngüye girer. Bıçağı tutamaz ama ç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 ama Bob çatalı çoktan kapmıştır; iş parçacığı bekleme durumuna geçer.
  4. Bob's Eat yöntemini çalıştıran iş parçacığı çağrılı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 yiyemediğini göreceksiniz).
Ayrıca thread penceresinde bir kilitlenme olup olmadığını kontrol edebilirsiniz. Programı çalıştırın ve Ctrl + Break tuşlarıyla 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ıçak kaptığı, ancak çatalının olmadığı görülebilir. Tot satırındaki Threads penceresine sağ tıklayın ve içerik menüsünden Switch to Thread komutunu 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 şüphelenmenizi sağlıyor.
Tek nesne ile senkronizasyon seçeneği (evdeki -sıcaklığı artıran 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, ona erişimin senkronize edilmesi gerekir. Hazır verilerin görünümü hakkında bekleyen iş parçacıklarını bilgilendirmek 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 kod ile çözülmüştür:

  • İş 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 bir koşul değişkeninin durumunu kontrol ederek çözmeye çalışmayın. sebep; 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ı, bekleyen kuyrukta 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şkenin olağan yoklamasından ç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ğrulaması bir döngü içinde yapıldığından, veri kullanımına geçiş (döngüden sonra) ancak 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 bloğu 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ğu olan 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 bThreadYeni 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 (Tamsayı Olarak ByVal Değeri)

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, Üret (39-50 satırlar) ve Tüketim (51-65 satırlar) akışlarında gerçekleşir. Alt Üretim prosedürü paranın kullanılabilirliğini 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. Şekilde 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 Bitiş

Ö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 Bitiş

Genel Alt BaşlangıçSayısı ()

GetE'ler ()

Alt Bitiş

Ö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 Bitiş

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ülkü

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şkilendirilmiş ç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 işler

Yeni Rastgele Karakterler Olarak Dim RC (10000000)

RC.StartCount ()

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

Alt Bitiş

İ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 Bitiş

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 belirgin ş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 Yeni (ByVa1 n As Integer, _

ByVal b As Windows.Forms.Button)

m_uzunluk = n - 1

m_Data = Yeni StringBuilder (mJength)

m_Button = b MakeString()

Alt Bitiş

Ö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 Bitiş

Genel Alt BaşlangıçSayısı ()

GetE'ler ()

Alt Bitiş

Ö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 Bitiş

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.

Ayrıca olayları kullanarak nesnelerin etkileşimini organize edemez. 06-event işçisi, RaiseEvent'in çağrıldığı aynı iş parçacığında ç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ştirme araçlarına sahip olması gerektiğini belirtir. NET Framework'te, GUI uygulamalarının yöntemlerini başka bir iş parçacığından ç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 (kalın olarak değiştirilen satırlar):

Ö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 Bitiş

Genel Alt GüncellemeDüğmesi ()

m_Button.Enabled = Doğru

Alt Bitiş

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.

Sebeplerden 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 birleştiricide 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 dışında 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ım bu açıklamalar, çok iş parçacıklı programlamanın neden bu kadar çok soruna yol açtığını daha iyi anlamanıza yardımcı olur - veya en azından çok iş parçacıklı programlarınızın garip davranışına daha az şaşırırsınız!

Basit bir çok iş parçacıklı uygulama oluşturmaya bir örnek.

Delphi'de çok iş parçacıklı uygulamalar oluşturmakla ilgili birçok soru nedeniyle doğdu.

Bu örneğin amacı, uzun süreli çalışmayı ayrı bir iş parçacığına alarak çok iş parçacıklı bir uygulamanın nasıl düzgün bir şekilde oluşturulacağını göstermektir. Ve böyle bir uygulamada, formdan (görsel bileşenler) akışa veri aktarmak ve bunun tersi için ana iş parçacığının çalışanla etkileşiminin nasıl sağlanacağı.

Örnek eksiksiz olduğunu iddia etmiyor, yalnızca iş parçacıkları arasındaki etkileşimin en basit yollarını gösteriyor. Kullanıcının düzgün çalışan çok iş parçacıklı bir uygulamayı "hızlı bir şekilde kör etmesine" (bundan ne kadar nefret ettiğimi kim bilir) izin vermek.
İçinde her şey ayrıntılı olarak yorumlanmıştır (bence), ancak herhangi bir sorunuz varsa, sorun.
Ama bir kez daha uyarıyorum: Akışlar kolay değil... Her şeyin nasıl çalıştığı hakkında hiçbir fikriniz yoksa, çoğu zaman her şeyin sizin için yolunda gitmesi ve bazen programın garip olmaktan daha fazlası olması gibi büyük bir tehlike vardır. Yanlış yazılmış çok iş parçacıklı bir programın davranışı, büyük ölçüde hata ayıklama sırasında yeniden üretilemeyen çok sayıda faktöre bağlıdır.

Yani bir örnek. Kolaylık olması için hem kodu yerleştirdim hem de arşivi modül ve form koduyla birlikte ekledim.

birim ExThreadForm;

kullanır
Windows, Mesajlar, SysUtils, Varyantlar, Sınıflar, Grafikler, Kontroller, Formlar,
İletişim kutuları, StdCtrls;

// veriyi akıştan forma aktarırken kullanılan sabitler
// pencere mesajları gönder
const
WM_USER_SendMessageMetod = WM_USER + 10;
WM_USER_PostMessageMetod = WM_USER + 11;

tip
// tThread'in soyundan gelen thread sınıfının tanımı
tMyThread = sınıf (tThread)
özel
SyncDataN: Tamsayı;
SyncDataS: Dize;
prosedür SyncMetod1;
korumalı
prosedür Yürüt; geçersiz kılmak;
halka açık
Param1: Dize;
Param2: Tamsayı;
Param3: Boole;
Durduruldu: Boole;
LastRandom: Tamsayı;
YinelemeNo: Tamsayı;
SonuçListesi: tStringList;

Yapıcı Oluştur (aParam1: String);
yıkıcı Yok et; geçersiz kılmak;
son;

// akımı kullanarak formun sınıf açıklaması
TForm1 = sınıf (TForm)
Etiket1: TL Etiket;
Not1: TMemo;
btnStart: TButton;
btnStop: TButton;
Düzenleme1: TDüzenle;
Düzenleme2: TDüzenle;
CheckBox1: TCheckBox;
Etiket2: TL etiketi;
Etiket3: TL etiketi;
Etiket4: TL etiketi;
prosedür btnStartClick (Gönderen: TObject);
prosedür btnStopClick (Gönderen: TObject);
özel
(Özel beyanlar)
MyThread: tMyThread;
prosedür EventMyThreadOnTerminate (Gönderen: tObject);
prosedür EventOnSendMessageMetod (var Msg: TMessage); mesaj WM_USER_SendMessageMetod;
prosedür EventOnPostMessageMetod (var Msg: TMessage); mesaj WM_USER_PostMessageMetod;

Halk
(Kamu beyanları)
son;

var
Form1: TForm1;

{
Durduruldu - Bir formdan bir akışa veri aktarımını gösterir.
Basit olduğu için ek senkronizasyon gerekli değildir
tek kelime türü ve yalnızca bir iş parçacığı tarafından yazılır.
}

prosedür TForm1.btnStartClick (Gönderen: TObject);
başlamak
Rastgele (); // Random () ile dizide rastgeleliğin sağlanması - akışla ilgisi yok

// Bir girdi parametresi geçirerek akış nesnesinin bir örneğini oluşturun
{
DİKKAT!
Akış oluşturucu, akış oluşturulacak şekilde yazılmıştır.
izin verdiği ölçüde askıya alındı:
1. Başlatma anını kontrol edin. Bu neredeyse her zaman daha uygundur çünkü
başlamadan önce bile bir akış kurmanıza izin verir, girişi iletin
parametreler vb.
2. Çünkü oluşturulan nesnenin bağlantısı form alanına kaydedilecek, ardından
ipliğin kendi kendini imha etmesinden sonra (aşağıya bakınız) iplik çalışırken
herhangi bir zamanda ortaya çıkabilir, bu bağlantı geçersiz hale gelecektir.
}
MyThread: = tMyThread.Create (Form1.Edit1.Text);

// Ancak, iş parçacığı oluşturulduğundan askıya alınır, ardından herhangi bir hatada
// başlatma sırasında (başlamadan önce), onu kendimiz yok etmeliyiz
// kullandığımız şey için try / blok hariç
denemek

// Alacağımız bir iş parçacığı sonlandırma işleyicisi atama
// akışın çalışmasının sonuçları ve ona olan bağlantının "üzerine yaz"
MyThread.OnTerminate: = EventMyThreadOnTerminate;

// Sonuçlar OnTerminate'de toplanacağı için, yani. kendini yok etmeden önce
// stream o zaman onu yok etme endişesini ortadan kaldıracağız
MyThread.FreeOnTerminate: = Doğru;

// Noktada, akış nesnesinin alanlarından giriş parametrelerinin geçirilmesine bir örnek
// zaten çalışmıyorken somutlaştır.
// Şahsen, bunu geçersiz kılınan parametreler aracılığıyla yapmayı tercih ederim.
// yapıcı (tMyThread.Create)
MyThread.Param2: = StrToInt (Form1.Edit2.Text);

MyThread.Stopped: = Yanlış; // bir tür parametre de, ama değişiyor
// iş parçacığı çalışma süresi
hariç
// thread henüz başlamadığından ve kendi kendini imha edemeyecek olduğundan, onu "manuel" olarak yok edeceğiz
FreeAndNil (MyThread);
// ve sonra istisnanın her zamanki gibi ele alınmasına izin verin
artırmak;
son;

// Thread nesnesi başarıyla oluşturulduğuna ve yapılandırıldığına göre, onu başlatma zamanı
MyThread.Resume;

ShowMessage ("Akış başladı");
son;

prosedür TForm1.btnStopClick (Gönderen: TObject);
başlamak
// İplik örneği hala mevcutsa, ondan durmasını isteyin
// Ve tam olarak "sor". Prensip olarak, biz de "zorlayabiliriz", ancak
// tüm bunların net bir şekilde anlaşılmasını gerektiren son derece acil durum seçeneği
// mutfak akışı. Bu nedenle burada dikkate alınmamıştır.
Atanmışsa (MyThread) o zaman
MyThread.Stopped: = Doğru
Başka
ShowMessage ("İş parçacığı çalışmıyor!");
son;

prosedür TForm1.EventOnSendMessageMetod (var Msg: TMessage);
başlamak
// senkron bir mesajı işleme yöntemi
// WParam'da tMyThread nesnesinin adresi, LParam'da iş parçacığının LastRandom'unun geçerli değeri
tMyThread (Msg.WParam) ile başlayın
Form1.Label3.Caption: = Format ("% d% d% d",);
son;
son;

prosedür TForm1.EventOnPostMessageMetod (var Msg: TMessage);
başlamak
// asenkron bir mesajı işleme yöntemi
// WParam'da IterationNo'nun geçerli değeri, LParam'da akışın LastRandom'unun geçerli değeri
Form1.Label4.Caption: = Biçim ("% d% d",);
son;

prosedür TForm1.EventMyThreadOnTerminate (Gönderen: tObject);
başlamak
// ÖNEMLİ!
// OnTerminate olayını işleme yöntemi her zaman main bağlamında çağrılır.
// thread - bu, tThread uygulaması tarafından garanti edilir. Bu nedenle, içinde özgürce yapabilirsiniz
// herhangi bir nesnenin herhangi bir özelliğini ve yöntemini kullan

// Her ihtimale karşı, nesne örneğinin hala var olduğundan emin olun
Assigned (MyThread) değilse Exit; // orada değilse, yapacak bir şey yok

// thread nesnesinin örneğinin thread çalışmasının sonuçlarını al
Form1.Memo1.Lines.Add (Biçim ("Akış, % d sonucuyla sona erdi",));
Form1.Memo1.Lines.AddStrings ((Gönderici olarak tMyThread) .ResultList);

// Akış nesnesi örneğine yapılan başvuruyu yok edin.
// İpliğimiz kendi kendini imha ettiğinden (FreeOnTerminate: = True)
// OnTerminate işleyicisi tamamlandıktan sonra, akış nesnesi örneği
// yok edildi (Ücretsiz) ve ona yapılan tüm referanslar geçersiz hale gelecek.
// Yanlışlıkla böyle bir bağlantıyla karşılaşmamak için MyThread'i sıkıştıracağız
// Bir kez daha not edeceğim - nesneyi yok etmeyeceğiz, sadece bağlantının üzerine yazacağız. Bir obje
// kendini yok et!
MyThread: = Sıfır;
son;

yapıcı tMyThread.Create (aParam1: String);
başlamak
// SUSPENDED akışının bir örneğini oluşturun (örnek oluştururken yoruma bakın)
devralınan Oluştur (Doğru);

// Dahili nesneler oluşturun (gerekirse)
ResultList: = tStringList.Create;

// Başlangıç ​​verilerini al.

// Parametreden geçirilen giriş verilerini kopyalayın
Param1: = aParam1;

// Bir akış nesnesinin yapıcısındaki VCL bileşenlerinden giriş verileri alma örneği
// Yapıcı bağlamda çağrıldığı için bu durumda bu kabul edilebilir.
// ana iş parçacığı. Bu nedenle VCL bileşenlerine buradan erişilebilir.
// Ama bundan hoşlanmıyorum çünkü bence iş parçacığı bir şey biliyorsa bu kötü
// orada bir form hakkında. Ama, gösteri için ne yapamazsın.
Param3: = Form1.CheckBox1.Checked;
son;

yıkıcı tMyThread.Destroy;
başlamak
// dahili nesnelerin imhası
FreeAndNil (SonuçListesi);
// tThread tabanını yok et
miras;
son;

prosedür tMyThread.Execute;
var
t: Kardinal;
s: dize;
başlamak
YinelemeNo: = 0; // sonuç sayacı (döngü numarası)

// Örneğimde, iş parçacığının gövdesi biten bir döngüdür
// veya Durduruldu değişken parametresinden geçirilen sonlandırma için harici bir "istek" ile,
// ya sadece 5 döngü yaparak
// Bunu "ebedi" bir döngü aracılığıyla yazmak benim için daha hoş.

True başlarken

Inc (YinelemeNo); // sonraki döngü numarası

LastRandom: = Rastgele (1000); // anahtar numarası - akıştan forma parametrelerin transferini göstermek için

T: = Rastgele (5) +1; // tamamlanmazsak uykuya dalmamız gereken süre

// Aptal iş (giriş parametresine bağlı olarak)
Param3 değilse o zaman
Inc (Param2)
Başka
Aralık (Param2);

// Bir ara sonuç oluştur
s: = Biçim ("% s% 5d% s% d% d",
);

// Sonuç listesine bir ara sonuç ekleyin
ResultList.Add(ler);

//// Ara sonucu bir forma geçirme örnekleri

//// Senkronize bir yöntemden geçmek - klasik yol
//// Dezavantajları:
//// - senkronize edilen yöntem genellikle akış sınıfının bir yöntemidir (erişmek için
//// akış nesnesinin alanlarına), ancak form alanlarına erişmek için
//// onu ve alanlarını (nesnelerini) "bilin" ki bu genellikle
//// programın organizasyonunun bakış açısı.
//// - yürütme tamamlanana kadar geçerli iş parçacığı askıya alınacak
//// senkronize yöntem.

//// Avantajlar:
//// - standart ve çok yönlü
//// - senkronize bir yöntemde kullanabilirsiniz
//// akış nesnesinin tüm alanları.
// önce gerekirse aktarılan verileri kaydetmeniz gerekir.
// nesne nesnesinin özel alanları.
SyncDataN: = YinelemeNo;
SyncDataS: = "Senkronizasyon" + s;
// ve ardından senkronize bir yöntem çağrısı sağlayın
Senkronize et (SyncMetod1);

//// Senkron mesaj gönderme ile gönderme (SendMessage)
//// bu durumda veri hem mesaj parametreleri (LastRandom) üzerinden geçirilebilir,
//// ve nesnenin alanları aracılığıyla, örneğinin adresini mesaj parametresinde geçirerek
//// akış nesnesinin - Tamsayı (Self).
//// Dezavantajları:
//// - iş parçacığı, form penceresinin tanıtıcısını bilmelidir
//// - Senkronizasyonda olduğu gibi, mevcut iş parçacığı şu ana kadar askıya alınacaktır.
//// ana iş parçacığı tarafından mesaj işlemeyi bitirmek
//// - her çağrı için önemli miktarda CPU zamanı gerektirir
//// (konu değiştirmek için) bu nedenle çok sık arama istenmez
//// Avantajlar:
//// - Senkronize Et'te olduğu gibi, bir mesajı işlerken kullanabilirsiniz
//// akış nesnesinin tüm alanları (elbette adresi iletildiyse)


//// diziyi başlat.
SendMessage (Form1.Handle, WM_USER_SendMessageMetod, Integer (Self), LastRandom);

//// Asenkron mesaj gönderimi ile transfer (PostMessage)
//// Bu durumda, mesaj ana iş parçacığı tarafından alındığında,
//// gönderen akış, örneğin adresini geçerek zaten bitmiş olabilir
//// akış nesnesi geçersiz!
//// Dezavantajları:
//// - iş parçacığı, form penceresinin tanıtıcısını bilmelidir;
//// - asenkronluk nedeniyle, veri aktarımı yalnızca parametreler aracılığıyla mümkündür
//// boyutu olan verilerin aktarımını önemli ölçüde zorlaştıran mesajlar
//// ikiden fazla makine kelimesi. Tamsayı vb. geçmek için kullanımı uygundur.
//// Avantajlar:
//// - önceki yöntemlerden farklı olarak, mevcut iş parçacığı
//// duraklatıldı ve hemen yürütmeye devam edecek
//// - senkronize bir çağrının aksine, bir mesaj işleyicisi
////, akış nesnesi hakkında bilgi sahibi olması gereken bir form yöntemidir,
//// veya veri yalnızca iletiliyorsa akış hakkında hiçbir şey bilmiyor
//// mesaj parametreleri aracılığıyla. Yani, iplik şekil hakkında hiçbir şey bilmiyor olabilir.
//// genellikle - yalnızca daha önce parametre olarak iletilebilen Handle'ı
//// diziyi başlat.
PostMessage (Form1.Handle, WM_USER_PostMessageMetod, IterationNo, LastRandom);

//// Olası tamamlamayı kontrol et

// Parametre ile tamamlanıp tamamlanmadığını kontrol edin
Durdurulursa, Kırılır;

// Ara sıra tamamlanıp tamamlanmadığını kontrol edin
IterationNo> = 10 ise Break;

Uyku (t * 1000); // t saniye uykuya dal
son;
son;

prosedür tMyThread.SyncMetod1;
başlamak
// bu metod Synchronize metodu ile çağrılır.
// Yani, tMyThread iş parçacığının bir yöntemi olmasına rağmen,
// uygulamanın ana iş parçacığı bağlamında çalışır.
// Bu nedenle, her şeyi yapabilir, ya da hemen hemen her şeyi :)
// Ama unutma, burada uzun süre "dalga geçme"ye değmez

// Geçirilen parametreleri, sahip olduğumuz özel alanlardan çıkarabiliriz
// aramadan önce kaydedildi.
Form1.Label1.Caption: = SyncDataS;

// ya akış nesnesinin diğer alanlarından, örneğin mevcut durumunu yansıtan
Form1.Label2.Caption: = Biçim ("% d% d",);
son;

Genel olarak, örnek, konuyla ilgili aşağıdaki akıl yürütmemden önce geldi ...

İlk önce:
Delphi'de çok iş parçacıklı programlamanın EN ÖNEMLİ kuralı:
Ana olmayan bir iş parçacığı bağlamında, formların özelliklerine ve yöntemlerine ve aslında tWinControl'den "büyüyen" tüm bileşenlere erişmek imkansızdır.

Bu, (biraz basitleştirilmiş) ne TThread'den miras alınan Execute yönteminde ne de Execute'dan çağrılan diğer yöntemlerde/prosedürlerde/işlevlerde, yasaktır görsel bileşenlerin tüm özelliklerine ve yöntemlerine doğrudan erişin.

Nasıl doğru yapılır.
Üniforma tarifleri yok. Daha doğrusu, o kadar çok ve farklı seçenek var ki, özel duruma bağlı olarak seçmeniz gerekiyor. Bu nedenle, makaleye atıfta bulunurlar. Onu okuyup anlayan programcı, belirli bir durumda bunu en iyi nasıl yapacağını anlayabilecektir.

Kısacası parmaklarınızda:

Çoğu zaman, çok iş parçacıklı bir uygulama, ya bir tür uzun vadeli çalışma yapılması gerektiğinde ya da işlemciyi fazla yüklemeyen birkaç şeyi aynı anda yapmak mümkün olduğunda olur.

İlk durumda, ana iş parçacığı içinde çalışmanın uygulanması, kullanıcı arayüzünün "yavaşlamasına" yol açar - iş yapılırken mesaj döngüsü yürütülmez. Sonuç olarak, program kullanıcı eylemlerine yanıt vermez ve örneğin kullanıcı onu taşıdıktan sonra form çizilmez.

İkinci durumda, iş dış dünya ile aktif bir alışverişi içerdiğinde, daha sonra zorunlu "kapalı kalma süresi" sırasında. Veri almayı / göndermeyi beklerken paralel olarak başka bir şey yapabilirsiniz, örneğin tekrar veri gönder / al.

Başka durumlar da var, ancak daha az sıklıkla. Ancak bu önemli değil. Şimdi bununla ilgili değil.

Şimdi, hepsi nasıl yazılır. Doğal olarak, biraz genelleştirilmiş, en sık görülen bir durum düşünülür. Yani.

Genel durumda, ayrı bir iş parçacığında yürütülen çalışmanın dört varlığı vardır (daha kesin olarak nasıl adlandıracağımı bilmiyorum):
1. İlk veriler
2. Aslında işin kendisi (ilk verilere bağlı olabilir)
3. Ara veriler (örneğin, iş yürütmenin mevcut durumu hakkında bilgi)
4. Çıktı verileri (sonuç)

Çoğu zaman, görsel bileşenler, verilerin çoğunu okumak ve görüntülemek için kullanılır. Ancak, yukarıda belirtildiği gibi, görsel bileşenlere akıştan doğrudan erişemezsiniz. Nasıl olunur?
Delphi geliştiricileri, TThread sınıfının Synchronize yöntemini kullanmanızı önerir. Burada nasıl kullanılacağını anlatmayacağım - bunun için yukarıda belirtilen makale var. Sadece şunu söylememe izin verin, uygulaması, doğru olanı bile, her zaman haklı değildir. İki sorun var:

İlk olarak, Synchronize aracılığıyla çağrılan bir yöntemin gövdesi her zaman ana iş parçacığı bağlamında yürütülür ve bu nedenle yürütülürken pencere mesaj döngüsü tekrar yürütülmez. Bu nedenle, hızlı bir şekilde yürütülmelidir, aksi takdirde, tek iş parçacıklı bir uygulama ile aynı sorunları yaşarız. İdeal olarak, Synchronize aracılığıyla çağrılan bir yöntem genellikle yalnızca görsel nesnelerin özelliklerine ve yöntemlerine erişmek için kullanılmalıdır.

İkinci olarak, bir yöntemi Senkronize ederek yürütmek, iş parçacıkları arasında iki geçişe ihtiyaç duyulması nedeniyle "pahalı" bir zevktir.

Dahası, her iki sorun da birbirine bağlıdır ve bir çelişkiye neden olur: bir yandan, ilkini çözmek için, Senkronize Et aracılığıyla çağrılan yöntemleri "öğütmeniz" gerekir ve diğer yandan, daha sonra daha sık çağrılmaları gerekir, değerli kaybederler. işlemci kaynakları

Bu nedenle, her zaman olduğu gibi, makul bir şekilde yaklaşmak ve farklı durumlar için akışın dış dünya ile farklı etkileşim yollarını kullanmak gerekir:

İlk veri
Akışa aktarılan ve çalışması sırasında değişmeyen tüm veriler, başlamadan önce bile aktarılmalıdır, yani. bir akış oluştururken. Bunları bir iş parçacığının gövdesinde kullanmak için yerel bir kopyasını oluşturmanız gerekir (genellikle TThread soyundan gelen alanlarda).
İş parçacığı çalışırken değişebilecek ilk veriler varsa, bu tür verilere ya eşitlenmiş yöntemler (Senkronize yoluyla çağrılan yöntemler) ya da iş parçacığı nesnesinin alanları (TThread'in soyundan gelen) aracılığıyla erişilmelidir. İkincisi biraz dikkat gerektirir.

Ara ve çıkış verileri
Burada yine birkaç yol var (benim tercihime göre):
- Ana uygulama penceresine asenkron mesaj gönderme yöntemi.
Genellikle işlemin ilerleyişi hakkında ana uygulama penceresine az miktarda veri aktarımıyla (örneğin tamamlanma yüzdesi) mesajlar göndermek için kullanılır.
- Ana uygulama penceresine senkronize olarak mesaj gönderme yöntemi.
Genellikle asenkron göndermeyle aynı amaçlar için kullanılır, ancak ayrı bir kopya oluşturmadan daha büyük miktarda veri aktarmanıza olanak tanır.
- Senkronize yöntemler, mümkünse mümkün olduğu kadar çok verinin aktarımını tek bir yöntemde birleştirmek.
Bir formdan veri almak için de kullanılabilir.
- Akış nesnesinin alanları aracılığıyla, karşılıklı olarak özel erişim sağlar.
Daha fazla ayrıntı makalede bulunabilir.

Eh. Kısa bir süre için işe yaramadı