Montoya montoya@csharpturk.net http://www.csharpturk.net Thread (İş Parçacığı) 03.04.2007 Bir çok yerde, makalede ya da kitapda threadler hakkında yazı okudum ama hepsi parça parçaydı ve birleştirmesi açısından çok sıkıntı çektim. Bu yazımda DotNet e ki Thread lerden bahsedecem sonra thread yonetimi ve senkronizasyonu, threadpoollardan ve en son olarak da asenkron programlamadan bahsedeceğim. Her yerde karşınıza çıkacak bir tanım yapacam ve bunu ingilizce yapacağım için özür dilerim. Thread is the execution part of the process Yani eğer programlamada yeniyseniz ve process ve threadleri karıştırıyorsanız diye koyduğum bu açıklama şu kanıyı gidersin diyedir. Thread yaratmadan thread olmaz. Herhangi bir programlama dilinde main fonksiyonu vardır ve İşletim sistemi main fonksiyonuna girmeden önce main fonksiyonu için bir thread oluştuturur. Process aslında fabrika, thread de fabrikadaki araçlar gibi düşünülebilir. Eğer siz kendiniz thread oluşturursanız kendi yazdığını herhangi bir kısım için bu multithreaded programming adını alır. Son olarak da şunu eklemek isterim siz task manager da bir process in önceliğini (prioritysini ) ayarlıdaığınızı düşünürsünüz, aslında yaptığınız o process e ait threadin prioritysidir. Bu iki önemli önbilgiden sonra uzuuuuun dokümanı okuyabilirsiniz. Şu soruyla başlayalım: Thread nedir, neden kullanılır? İşte bu soruyu anlarsak kullanım açısından bazen programları kullanıcı açısından rahatlatabiliriz. Örnek olarak şu anda bu makelemi yazdığım kelime işlemci (microsoft word) spell checking yapıyor arkada, belli aralıklara dökümanımı kaydediyor, yazımı yazmamı sağlıyor..vs. Sonuçta dokumanı kaydederken beni rahatsız etmemesinin sebebi ya da yazarken durdurup ben Microsoft Office Word um bekle iki dakika da bi doğru mu yazmışsın kontrol edeyim demiyor. Arkada işini yapması için başka bir kanal açıyor ve bu kanal aracılığı ile de yazımı kontrol ediyor. İşletim sistmelerine girecek olursak, multitasking in porgram bazına indirilmiş hali diyebiliriz. Başka bir örnek verirsek : Bir programı yüklerken kullanıcıya ne kadar süre kaldığını söylemesi ve arkada da gerekli dosyaları da kopyalayıp, ayarlaması gerek. 1. Threads Virtual Machine ler temeline bakalırsa işletim sistemine benzer mesela işletim sistemleri processleri yönitirken onlar için yer ayarlarken Virtual Machine ler de JVM ve Microsoft.NET Framework işletim sistemiyle kendi process'i için araya girerek aracılık yapar. İşletim sistemindeki threadleri işletim sistemi yönetirken daha üst katmandaki threadleri de Framework de System.AppDomain lightweight threadleri yönetir. AppDomain içinde bir veya daha fazla thread olabilir. 1/11
Threadlere bakıldığında biraz pahalıdır nedeni ise programcı ve mühendis için en pahalısı ise kalitelidir mantığından çok, pahalı ise kolaydır, o zaman threadler kolaydır ancak performanslı değildir demek daha uygun olur. Nedeni ise bir thread yaratıldığı zaman düzgün bir şekilde çalışması için tüm CPU registerları, gerekli kod ve stack alanı kopyalanır. dotnet te Threadlerin kullanılması için öncelikle System.Threading namespace ine bakmamız gerekmektedir. Çünkü gerekli sınıflar o namespace içinde yer almaktadır. Küçük uygulamamıza geçmeden önce en son söyleyeceğim şeyse COM mimarisinde threadler unmanaged code a dayanırken.net mimarisinde managed code a dayanmaktadır. O yüzden işletim sistmi dersimizdeki bilgilerimizi yoklarsak.net Threadleri user mode threadlere girer diyebiliriz. 1.1 Process Synchronization 1.1.1 Critical Section Critical Section denilen olay kısaca birden fazla threadin aynı kaynağa erişmesine denir. Mesela CD yazarken onu okuyamayız ya da daha derine inersek iki tane process aynı anda bir dosyaya yazmaya kalkarsa birinden biri hata çıkaracaktır. İşte her processde critical section denilen yer bir kod parçası olup global değişkeni değiştirdiği,update ettiği ya da bir file a bir şeyler yazdığı kısımdır. Gerçek hayata uyarlarsak bir erkekiğin iki tane kız arkadaşı olabilirken bir kızın sadece bir erkek arkadaşı olabilir, erkek burda process olup kız arkadaş da resource olmaktadır.erkeğin kız arkaşıyla harcadığı zaman ise CriticalSectiondır. Her process criticalsection a girerken bir istekte bulunur bu entry sectiondır ve çıkarkenki kısmı da exit sectiondır. Zaten bu konudan Process cooperation çıkmaktadır. ExitSection dan sonra kodun geri kalan kısmına da Remiander Section adı verilir. Eğer anlattığımı özetlemek gerekirse aşağıdaki kısım gibi olmaktadır. Do { EntrySection Critical Section ExitSection } while(true) Mutual Exlusion ise eğer bir process entry criticalsection dan sonra bir işlem yapıyorsa o anda hiçbir process criticalsection da olamaz.mesela bir process bir file s erişmiş ya da bir global değişken update edilirken diğer threadler bu global değişkeni update etmeye kalkarsa okunacak olan data istenildiği gibi olmaz ( Dirty Read ). 1.1.2 Semaphore Yukarda bahsettiğimiz gibi aynı anda bir resource a sadece bir thread erişebilir o resource sadece bir process ilgilenebilir. İstediğimiz soruna çözüm iki yöntemle olabilir. 2/11
Birincisi hardware tanbanlı bir synchronization ikinsici ise software tabanlı bir çözüm. Semaphore software tabanlı bir çözüm olup oldukça basit bir implementasyonu vardır. Semaphore iki kısımdan oluşup bunlar da wait ve signal dır. Aşağıdaki gibi bir çözüm semaphore u ifade eder. wais(s) { } while S<=0 ; //Nop = No operation S-- signal ın da tanımı signal(s) { } S++; Signal ve wait işlemi bölünmeden yani bir process semaphore a girdikten sonra diğer processler bu semaphordaki integer değerini değiştirmemelidir. Mutex lock larin temelde çok bir farklılığı yoktur semaphoreden. Semaphore u ikiye ayırırsak counting semaphore ve binary semaphore diye yukarda yazdığımız tanım counting semaphore olup binary sempahore sa genelde mutex lock olarak bilinir. Daha doğru bir şekilde ifade etmek gerekirse semaphore mutex ın bir extension ı olup mutex lerde bir thread critical section da olduğunda başka threadler critical section da olamaz ancak semaphore da işler değişir, n tane thread critical section da olabilir. Bu durum bir resource ın birden çok örneği (instance) olduğunda yararlıdır. Semaphore un alacağı n sayısını 1 olara ayarlarsak bu mutex olur. 1.1.3 Monitor Her zamanki gibi çoğu programcının semaphore ları yanlış kullanmasından dolayı bilim adamları üst düzey bir implementation olan Monitor ı çıkardı. Kişisel görüşümü yazmam gerekirse çoğu Object-Oriented Dilde yanlış multiple inheritance kullanılmasından dolayı multiple class inheritance yoktur. O yüzden bir dil ya da bir uygulma ne kadar eski ise güzeldir diyebilirim. Örnek olaral verilebilecek problemler wait(mutex)... critical section 3/11
... wait(mutex) Yukardaki probleme sadece şunu diyebilirm bekle babam bekle problemidir. İşte bunun gibi bir çok problemi çözen monitor aşağıdaki gibi çalışır ve kod içersinde kullanılması çoğu programcı açısından kolaydır. monitor { //paylaşılan değişkenler kısaca global variables procedure p1(...) { Update Global Variables } procedure p2(...) { Update Global Variables } } x,y gibi iki tane durum belirtirsek x.wait() ve x.siganl() diyerek bu durumları update edecek kod çalışır ve sadece bir thread bir resource a erişir 1.2 Deadlocks Bu kadar konu içersinde bu konu en sevdiğim konudur çünkü hayatta durmadan başıma gelir. Örnek olarak kapıda karşılaşan ve sadece tek kişinin geçebileceği bir kapıda biri diğerini beklerken diğeri de karşısındakini beklerse bu bir deadlockdır. Burda iki insan iki thread olarak düşünebiliriz. Ya da hoca soru sorduğu zaman sınıfta herkes elbet biri cevap verir diye beklemesi kısaca bu tip örnekler çoğaltılabilir ama teknik olarak açıklaması şudur birden fazla thread aynı anda bir resource erişmeye kalkığında hepsi wait state e girip diğer threadleri beklerse bu bir deadlock'tır. Sonuçta her thread birbirini beklediğinde hiç biri signal konumuna geçmeyeceğinden ömür boyu birbirlerini beklerler. Ya da iki thread iki resource olsun ve aynı anda karşılıklı resource istesin. Tabii bu deadlock ın bir de kardeşi var o da starvation iki kardeş birbirine huy olarak ne kadar farklıysa bile aynı ana babadan çıktığı için birbirine tip olarak benzer. Aynı şekilde starvation da bir thread bir resourcesa eriştikten sonra onu 4/11
bırakmıyorsa diğerleri wait konumunda beklerler. bu da starvationdır sonuçta deadlockın türevi ya da kardeşi diyebiliriz. Örnek vermek gerekirse bir hoşlandığınız kızın ömür boyu biriyle çıkmasıdır yani siz thread olduğunuzdan ömür boyu starvation çekeceksinizdir, algoritmanız o resource dolu ise başka resourcesa gitme diyorsa. 1.2.1 Resource Allocation Graph Resource Allcation Graph yazdığınız ya da yazağınız programda deadlock oluşabilir mi oluşamaz mı onu anlamaya yarayan graph tabanlı bir metoddur. Graph ın özelliği olan çizge ve düğümlerden oluşur. Matematiksel olarak ifade etmek gerekirsek Processler P kümesi Resourcelar R kümesi ve E de Hangi düğümden nereye gidebileceğimi bulunduran küme olsun P = {P1, P2, p3} R = {R1, R2, R3, R4} E = {P->R1, P2->R3, R1->P2, R2->P2, R2->P1,R3->P3} P1,P2 ve P3 gibi üç tane process Bir tane R1, iki tane R2 bir tane R3 ve üç tane R4 olsun ve yukardaki kümleri kullanarak resource allocation graphını çizelim 5/11
Artık graphımıza bakarak durumu inceleyelim Process P1, R2 kaynağının bir örneğini tutuyor ve R1 kaynağını bekliyor Process P2, R1 in ve R2 nin bir örneğini tutuyor ve R3 resourcesunu bekliyor. Process P3 ise R3 kaynağını tutuyor. Eğer bu graph da herhangi bir şekilde cycle olsaydır o zaman deadlock a müsait olur diyebilirdik ve deadlock oluşur derdik. Bu graph baktığımızda okların yönlerini takip ederek hiç bir şekilde başladığım noktaya ulaşamadığımdan deadlock oluşmaz diyebilirim. İkinci bir örnek vermek gerekirse : P = {P1, P2, P3} R = {R1,R2,R3} E = {R3->P1, R1->P2,R3->P2,P2->R2,R2->P3,P3->R3} Bir tane R1 1 tane R2 iki tane ise R3 kaynağının örneklerinden olsun. Graphını çizersek 6/11
P1 process i R3 resourcesunu tutuyor. P2 process i R3 ve R1 resourcesunu tutup R2 resourcesunu bekliyor. P3 processi R2 resourcesunu tutuğ R3 resourcesunu bekliyor. Evet cycle var o zaman deadlock oluşabilir! Olaşabilir çünkü doğru deadlock onlenme metodları ile bu olayın önüne geçilebilir. Kısaca resource allocation graph sadece oluşabilir (kesinlik anlamı YOK) olmasına bakıyor, gerisi yazılımcıya kalmıştır 1.2.2 Deadlock Onlenmesi Deadlock önlenmesi esasen biraz mantıklı düşünmeye dayalı bir konu. Sonuçta bir program yazarken yaptığımız en büyük hatalardan biri File I/O işlemi yapacaksak write ya da read çağırısı yollamadan dosyayı sahiplenmemiz.mesela cd den okuduğumuz datayı bir file a yazalım aynı anda iki kaynağa erişip okuduktan sonra file a yazmak biraz saçmalıktır. İlk cd ye erişilip data alınmalı sonra da memorydeki data file a yazılmalıdır. Readonly datalar için bu çok önemlidir. Bir processin resource tablsundaki resourcelara maximum şekilde deadlock oluşmadan erişebiliyorsa buna safe state denir. Eğer bir sistem için safe state denilebiliyorsa o sistemde safe sequance vardır. Resource Allocation graph dan daha kötü ama anlaşılması daha kolay bir yöntem olan Banker algoritması ise sistemin safe state durumunda olup olmayacağını anlayan bir algoritmadır. Banker algoritmasından bahsetmek gerekirse aşağıdai gibi bir tablodan 12 tanedvd romu olan bir serverı nasıl safe state sokabiliriz onu incelememize yarar.hangi 7/11
processlerin daha önce işlenmesi gerektiğini, sıralama yapılmasını ve maximum verimde sistemin kullanılmasını çıkaran bir algoritmadır. Görüşümise bu algoritma cidden kolay ve pratik zekanın ürünüdür. Processler Maximum İhtiyacı Şimdiki İhtiyacı P1 10 5 P2 4 2 P3 9 2 Aşağıdaki verdiğim tabloyu incelersek Processler Allocation MAX Available A B C A B C A B C P0 0 1 0 7 5 3 3 3 2 P1 2 0 0 3 2 2 P2 3 0 2 9 0 2 P3 2 1 1 2 2 2 P4 0 0 2 4 3 3 Evet yukardaki tabloda process kümesi olan P = {P0, P1, P2,P3,P4} maximum resource kümesi olan R= {A, B, C} den maximum erişmesi gereken sayı ve yapatığı allocation için verilmiş sayılar ve boşta bulunan resourceların sayılarıdır. Şimdi Banker Algoritmasını kullanırsak bir tablo daha karşımıza çıkıyor. Processler Need A B C P0 7 4 3 P1 1 2 2 P2 6 0 0 P3 0 1 1 P4 4 3 1 Bu tabloya da Need Tablosu dersek Need tablosunun need kolonunda şu sorunun cevabını vermektedir: Benim şuan ne kadar resource a ihtiyacım var? Yani Need = MAX Allocation. Şimdi Yukardaki tabloyu ve Need tablosunu kullanarak Processlerimizi safe state olacak şekilde sıraya koyalım. P0 = A dan 7 tane B den 4 tane C den 3 tanedir ama available resource sayısı ABC için 3 3 2 dir yani P0 processi 7 tane A 8/11
resourcesuna ihtiyaç duyarken available A resource sayısı 3 tanedir. Aynı process için B den 4 tane resource ihtiyacı vardır ancak available B resource sayısı 3 tür. Bu durumda safe state olmayacaktır. P1 proccess ine bakarsak A dan 1 tane B den 2 tane C den de 2 tane resource ihtiyacı vardır available resource sayısına bakarsak A için 3 tane B için 3 tane C içinse 2 tanedir. Bu durumda P1 process i ilk olarak çalıştırabiliriz. P1 processi ilk olarak çalışırsa elindeki resourcelarla işi bittikten sonra geri vereceğinden available resource sayısı 3 3 2 den 5 3 2 ye çıkar. Aynı şekilde devam edip execution için processleri sıralarsak <P1,P3,P4,P2,P0 > sıralamasını elde ederiz. Umarım örnek iyi anlaşılmıştır sanki bir banker işi derseniz zaten adından da anlaşılacağı gibi bankacı anlamına gelen banker konmuştur. Kısaca algoritmayı özetlersek Available = available request Allocation = Allocation + request Need = need - request 1.2.3 Deadlock Bulunması Eğer bir sistem deadlock önlenmesi için herhangi bir şey yapmadıys ya da herhangi bir algoritma geliştirmediyse o zaman sistemin deadlock a girmesi gibi doğal bir şeyden kaçamazsınız. Genelde yapılan sisteme reset atmak oluyor. Bu bölümde size deadlock önleme algoritması barındırmayan bir sistemde nasıl deadlock bulunur ve önlenir onu anlatacağım. Sisteminizde her bir kaynağın sadece bir örneği vasa o zaman gene resource allocation graph ını çizebilirsiniz. Ancak bu çizdiğiniz graph ın adı wait-for graph olur. Gene aynı şekilde cycle olup olmadığına bakarak dead-lock oluşup oluşmayacağına bakılabilir. 9/11
Eğer sisteminizde çok şekilde deadlock oluşuyorsa deadlock önleyici algoritmanız sürekli çalışmalıdır. Mesela windows 98 de deadlock a girince reset atmanız gibi aynı şekilde yazılımınıza tekrar reset atılmalıdır ya da en az tercih edilen yöntem deadlock a giren processin kapatlıp yeniden başlatılmasıdır. Eğer başka processiniz kapatılması gerekiyorsa ve deadlock bulucu algoritmanız bunu öngörüyorsa o zaman seçmeniz gerekn process in belli kriterleri olmalıdır ve şu soruların cevaplarını vermelidir? Process in önceliği nedir? Process in kapatılıp açılması halinde sisteme ne kadar zarar verir ne kadar veri kaybı olur Her kaynak için bir istek geldiğinde deadlock önleyici algoritmanızın çalışması esasen performans düşürücüdür. Resource allocation graph daki oluşabilecek kısaca potansiyel oluşturabilecek threadler istek geldiğinde ya da belli kısır yerlerde çalıştırılması sistem için performans yükseltici bir olaydır 1.3 Win32 Thread Programlama Bu konu esasen başlı başına bir konu, bir sonraki yazıda detayları ile açıkladıktan sonra.net te thread programlamaya geçeceğim. Bilindiği gibi Microsoft Teknolojilerinin çoğu win32 tabanlı programlamadan meydan geliyor. Windows 2000 den 2003 server a kadar çoğu yerde.net kullanılmıyor. dotnet kullanım amacı işletim sistemi geliştirmek değil hızlı ve güvenilir bir şekilde yazılım yapmaktır. Win32 programalada framework den en temel farklılığı arada katman olmadan işletim sistemi çağrıları yapabilmesidir. Bu yüzden CreateThread() gibi bir fonksiyonda arada bir katman olmadan kernel mode threadler çalışacağından daha hızlı olacaktır. 1.3.1 CreateProcess() 1.3.2 CreateThread() 1.3.3 WaitForSingleObject() 1.3.4 WaitForMultipleObjects() 1.3.5 CreateEvent() 1.3.6 CreatePipe() 1.3.7 Shared Memory 1.3.8 Win32 Thread Senkronizasyonu 10/11
1.3.8.1 Mutex 1.3.8.2 Semaphore 1.3.8.3 Events 1.3.8.4 Interlocks 1.3.9 ThreadPools 1.4 dotnet te Thread Programlama 11/11