8. Çalıştırma iş parçacıkları
8.1. Giriş
Bir uygulama başlatıldığında, uygulama "iş parçacığı" olarak adlandırılan bir yürütme akışında çalışır. thread'i modelleyen .NET sınıfı, System.Threading.Thread sınıfıdır ve aşağıdaki gibi tanımlanmıştır:

Bu sınıfın özelliklerinden ve yöntemlerinden yalnızca bazılarını kullanacağız:
şu anda yürütülmekte olan iş parçacığını verir | |
iş parçacığının adı | |
iş parçacığının aktif olup olmadığını (true) veya olmadığını (false) gösterir | |
bir iş parçacığının yürütülmesini başlatır | |
bir iş parçacığının çalışmasını kalıcı olarak durdurur | |
bir iş parçacığının çalışmasını n milisaniye süreyle durdurur | |
bir iş parçacığının yürütülmesini geçici olarak askıya alır | |
askıya alınmış bir iş parçacığının yürütülmesini yeniden başlatır | |
engelleyici işlem - bir sonraki komuta geçmek için iş parçacığının bitmesini bekler |
Bir sınıfın Main işlevinin yürütüldüğü ana yürütme iş parçacığının varlığını vurgulayan ilk uygulamaya bakalım:
' iş parçacığı kullanımı
Imports System
Imports System.Threading
Public Module thread1
Public Sub Main()
' mevcut iş parçacığını başlatma
Dim main As Thread = Thread.CurrentThread
' görüntüleme
Console.Out.WriteLine(("Thread courant : " + main.Name))
' ad değiştiriliyor
main.Name = "main"
' doğrulama
Console.Out.WriteLine(("Thread courant : " + main.Name))
' sonsuz döngü
While True
' görüntüleme
Console.Out.WriteLine((main.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
' geçici durdurma
Thread.Sleep(1000)
End While
End Sub
End Module
Ekran çıktısı:
dos>thread1
Thread courant :
Thread courant : main
main : 06:13:55
main : 06:13:56
main : 06:13:57
main : 06:13:58
main : 06:13:59
Yukarıdaki örnek şu noktaları göstermektedir:
- Main işlevi bir iş parçacığında sorunsuz bir şekilde çalışır
- Thread.CurrentThread aracılığıyla bu iş parçacığının özelliklerine erişilebilir
- Sleep yönteminin rolü. Burada, Main'i çalıştıran iş parçacığı, iki görüntüleme arasında düzenli olarak 1 saniye süreyle uyku moduna geçmektedir.
8.2. Çalıştırma iş parçacıklarının oluşturulması
Farklı yürütme iş parçacıklarında kod parçalarının "eşzamanlı" olarak yürütüldüğü uygulamalar olabilir. thread'lerin eşzamanlı olarak yürütüldüğünü söylediğimizde, genellikle yanlış bir ifade kullanmış oluruz. Hala sıklıkla olduğu gibi, makinede tek bir işlemci varsa, thread'ler bu işlemciyi paylaşır: her biri sırayla kısa bir süre (birkaç milisaniye) boyunca işlemciyi kullanır. Bu durum, yürütme paralelliği yanılsamasını yaratır. Bir thread'e ayrılan süre, varsayılan bir değere sahip olan ancak programlama yoluyla da belirlenebilen önceliği de dahil olmak üzere çeşitli faktörlere bağlıdır. Bir thread işlemciye sahip olduğunda, normalde kendisine ayrılan sürenin tamamı boyunca işlemciyi kullanır. Ancak, süre dolmadan önce işlemciyi serbest bırakabilir:
- bir olayı bekleyerek (wait, join, suspend)
- belirli bir süre boyunca uyku moduna geçerek (sleep)
- Bir T iş parçacığı ilk olarak oluşturucusu tarafından oluşturulur
ThreadStart, delegate türündedir ve parametresiz bir fonksiyonun prototipini tanımlar:
Klasik bir yapı şöyledir:
Parametre olarak geçirilen run işlevi, iş parçacığı başlatıldığında yürütülecektir.
- T iş parçacığının yürütülmesi, T.Start() tarafından başlatılır: T'nin oluşturucusuna geçirilen [run] işlevi, daha sonra T iş parçacığı tarafından çalıştırılacaktır. T.start() komutunu çalıştıran program, T görevinin bitmesini beklemez: hemen bir sonraki komuta geçer. Böylece paralel olarak çalışan iki görev ortaya çıkar. Bu görevler, gerçekleştirilmesi gereken ortak işin ne aşamada olduğunu öğrenmek için genellikle birbirleriyle iletişim kurabilmelidir. Bu, iş parçacıklarının senkronizasyonu sorunudur.
- thread, başlatıldıktan sonra bağımsız olarak çalışır. Yürütmekte olduğu start işlevi işini bitirdiğinde durur.
- T görevine belirli sinyaller gönderilebilir:
- T.Suspend() işine geçici olarak durmasını söyler
- T.Resume() işine devam etmesini söyler
- T.Abort() komutu, göreve kalıcı olarak durmasını söyler
- Ayrıca T.join() ile görevin tamamlanmasını bekleyebiliriz. Bu, engelleyici bir komuttur: bu komutu çalıştıran program, T görevi işini bitirene kadar engellenir. Bu, bir senkronizasyon yöntemidir.
Şu programı inceleyelim:
' seçenekler
Option Strict On
Option Explicit On
' ad alanları
Imports System
Imports System.Threading
Module thread2
Public Sub Main()
' geçerli iş parçacığını başlat
Dim main As Thread = Thread.CurrentThread
' İş parçacığına ad atama
main.Name = "main"
' çalışma iş parçacıkları oluşturma
Dim tâches(4) As Thread
Dim i As Integer
For i = 0 To tâches.Length - 1
' i iş parçacığı oluşturulur
tâches(i) = New Thread(New ThreadStart(AddressOf affiche))
' iş parçacığının adı belirleniyor
tâches(i).Name = "tache_" & i
' i iş parçacığının yürütülmesini başlatma
tâches(i).Start()
Next i
' ana işlevin sonu
Console.Out.WriteLine(("fin du thread " + main.Name))
End Sub
Public Sub affiche()
' çalışmanın başladığı gösterilir
Console.Out.WriteLine(("Début d'exécution de la méthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
' 1 saniye boyunca uyku moduna geçme
Thread.Sleep(1000)
' çalışma sonu gösteriliyor
Console.Out.WriteLine(("Fin d'exécution de la méthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
End Sub
End Module
Main işlevini çalıştıran ana iş parçacığı, affiche statik yöntemini çalıştırmakla görevli 5 iş parçacığı daha oluşturur. Sonuçlar şöyledir:
dos>thread2
fin du thread main
Début d'exécution de la méthode affiche dans le Thread tache_0 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_1 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_2 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_3 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_4 : 05:27:53
Fin d'exécution de la méthode affiche dans le Thread tache_0 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_1 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_2 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_3 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_4 : 05:27:54
Bu sonuçlar oldukça aydınlatıcıdır:
- öncelikle, bir iş parçacığının yürütülmesinin başlatılmasının engelleyici olmadığı görülüyor. Main yöntemi, 5 iş parçacığının yürütülmesini paralel olarak başlattı ve onlardan önce kendi yürütülmesini tamamladı.
işlemi, tâches[i] iş parçacığının yürütülmesini başlatır, ancak bu işlem tamamlandıktan sonra, iş parçacığının yürütülmesinin bitmesini beklemeden yürütme hemen bir sonraki komutla devam eder.
- Oluşturulan tüm iş parçacıkları affiche yöntemini yürütmelidir. Yürütme sırası öngörülemez. Örnekte yürütme sırası, yürütme isteklerinin sırasını takip ediyor gibi görünse de, bundan genel bir sonuç çıkarılamaz. İşletim sisteminde burada 6 iş parçacığı ve bir işlemci bulunmaktadır. İşletim sistemi, kendi kurallarına göre işlemciyi bu 6 iş parçacığına dağıtacaktır.
- Sonuçlarda Sleep yönteminin bir sonucu görülmektedir. Örnekte, affiche yöntemini ilk olarak 0 numaralı iş parçacığı yürütmektedir. Çalıştırma başlangıç mesajı görüntülenir, ardından Sleep yöntemini çalıştırır ve bu yöntem iş parçacığını 1 saniye boyunca askıya alır. Böylece işlemciyi kaybeder ve işlemci başka bir iş parçacığı için kullanılabilir hale gelir. Örnekte, işlemciyi 1 numaralı iş parçacığının alacağı görülmektedir. İş parçacığı 1, diğer iş parçacıklarıyla aynı yolu izleyecektir. İş parçacığı 0’ın 1 saniyelik bekleme süresi sona erdiğinde, yürütmesi devam edebilir. Sistem ona işlemciyi tahsis eder ve affiche yönteminin yürütmesini tamamlayabilir.
Programımızı değiştirerek Main yöntemini şu komutlarla sonlandıralım:
Yeni programın çalıştırılması sonucunda şu çıktı elde edilir:
Main işlevi tarafından oluşturulan iş parçacıkları çalıştırılmıyor. Bunun nedeni şu komuttur:
komutu bunu yapmaktadır: sadece Main iş parçacığını değil, uygulamanın tüm iş parçacıklarını silmektedir. Bu sorunun çözümü, Main yönteminin, kendi çalışmasını sonlandırmadan önce oluşturduğu iş parçacıklarının çalışmasının bitmesini beklemesidir. Bu, Thread sınıfındaki Join yöntemi ile yapılabilir:
' tüm iş parçacıklarının yürütülmesinin bitmesini bekleme
For i = 0 To tâches.Length - 1
' i iş parçacığının yürütülmesinin bitmesini bekleme
tâches(i).Join()
Next i 'for
' işlem sonu
Console.Out.WriteLine(("fin du thread " + main.Name))
Environment.Exit(0)
Böylece şu sonuçlar elde edilir:
Début d'exécution de la méthode affiche dans le Thread tache_1 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_2 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_3 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_4 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_0 : 05:34:48
Fin d'exécution de la méthode affiche dans le Thread tache_2 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_1 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_3 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_0 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_4 : 05:34:50
fin du thread main
8.3. İş parçacıklarının önemi
Artık varsayılan bir iş parçacığının varlığını ortaya çıkardık; bu iş parçacığı Main yöntemini yürütür ve başka iş parçacıkları nasıl oluşturulacağını da öğrendik. Şimdi iş parçacıklarının bizim için ne kadar önemli olduğuna ve bunları burada neden ele aldığımıza değinelim. İş parçacıklarının kullanımına son derece uygun bir uygulama türü vardır: İnternet’teki istemci-sunucu uygulamaları. Böyle bir uygulamada, S1 makinesinde bulunan bir sunucu, C1, C2, ..., Cn gibi uzak makinelerde bulunan istemcilerin isteklerine yanıt verir.
![]() |
Her gün bu şemaya uyan internet uygulamalarını kullanıyoruz: web hizmetleri, e-posta, forumlara göz atma, dosya aktarımı... Yukarıdaki şemada, S1 sunucusu Ci istemcilerine aynı anda hizmet vermelidir. Müşterilerine dosya ileten bir FTP (Dosya Aktarım Protokolü) sunucusunu örnek alırsak, bir dosya aktarımının bazen birkaç saat sürebileceğini biliriz. Elbette bir müşterinin bu kadar uzun süre boyunca sunucuyu tek başına işgal etmesi söz konusu olamaz. Genellikle yapılan şey, sunucunun müşteri sayısı kadar yürütme iş parçacığı oluşturmasıdır. Her iş parçacığı, belirli bir müşteriyle ilgilenmekle görevlendirilir. İşlemci, makinedeki tüm aktif iş parçacıkları arasında döngüsel olarak paylaşıldığından, sunucu her bir müşteriyle biraz zaman geçirir ve böylece hizmetin eşzamanlılığını sağlar.
![]() |
8.4. Paylaşılan kaynaklara erişim
Yukarıda bahsedilen istemci-sunucu örneğinde, her iş parçacığı bir istemciye büyük ölçüde bağımsız bir şekilde hizmet eder. Bununla birlikte, iş parçacıkları, özellikle paylaşılan kaynaklara erişim için istemcilerine talep edilen hizmeti sunmak üzere işbirliği yapmak zorunda kalabilirler. Yukarıdaki şema, örneğin her gişede bir memurun bir müşteriye hizmet verdiği büyük bir idari kurumun gişelerini andırır. Zaman zaman bu memurların, müşterileri tarafından getirilen belgelerin fotokopilerini çekmeleri gerektiğini ve sadece bir fotokopi makinesi olduğunu varsayalım. İki görevli fotokopi makinesini aynı anda kullanamaz. Görevli i, fotokopi makinesinin görevli j tarafından kullanıldığını görürse beklemek zorunda kalır. Bu duruma “paylaşılan kaynağa erişim” denir ve bilgisayar bilimlerinde yönetimi oldukça zordur. Şu örneği ele alalım:
- Bir uygulama n adet iş parçacığı oluşturacak; burada n, parametre olarak verilir
- paylaşılan kaynak, oluşturulan her iş parçacığı tarafından artırılması gereken bir sayacıdır
- Uygulamanın sonunda sayaç değeri görüntülenir. Dolayısıyla n değerini bulmamız gerekir.
Program şu şekildedir:
' seçenekler
Option Explicit On
Option Strict On
' iş parçacıklarının kullanımı
Imports System
Imports System.Threading
Public Class thread3
' sınıf değişkenleri
Private Shared cptrThreads As Integer = 0
Public Overloads Shared Sub Main(ByVal args() As [String])
' kullanım kılavuzu
Const syntaxe As String = "pg nbThreads"
Const nbMaxThreads As Integer = 100
' argüman sayısının kontrolü
If args.Length <> 1 Then
' hata
Console.Error.WriteLine(syntaxe)
' durdurma
Environment.Exit(1)
End If
' argüman kalitesi denetimi
Dim nbThreads As Integer = 0
Try
nbThreads = Integer.Parse(args(0))
If nbThreads < 1 Or nbThreads > nbMaxThreads Then
Throw New Exception
End If
Catch
' hata
Console.Error.WriteLine("Nombre de threads incorrect (entre 1 et " & nbMaxThreads & ")")
' son
Environment.Exit(2)
End Try
' iş parçacıklarının oluşturulması ve üretilmesi
Dim threads(nbThreads - 1) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' oluşturma
threads(i) = New Thread(New ThreadStart(AddressOf incrémente))
' adlandırma
threads(i).Name = "tache_" & i
' başlatma
threads(i).Start()
Next i
' iş parçacıklarının bitmesini bekleme
For i = 0 To nbThreads - 1
threads(i).Join()
Next i ' affichage compteur
Console.Out.WriteLine(("Nombre de threads générés : " & cptrThreads))
End Sub
Public Shared Sub incrémente()
' iş parçacığı sayacını artırır
' sayaç okuma
Dim valeur As Integer = cptrThreads
' izleme
Console.Out.WriteLine(("A " + DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads))
' bekleme
Thread.Sleep(1000)
' sayaç artırma
cptrThreads = valeur + 1
' izleme
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a écrit la valeur du compteur : " & cptrThreads))
End Sub
End Class
Daha önce incelediğimiz iş parçacığı oluşturma kısmına fazla değinmeyeceğiz. Bunun yerine, her iş parçacığı tarafından statik sayaç cptrThreads'i artırmak için kullanılan incrémente yöntemine odaklanalım.
- sayaç okunduğunda
- iş parçacığı 1 saniye durur. Dolayısıyla işlemciyi kaybeder
- sayaç artırılır
- adım, iş parçacığını işlemciyi kaybetmeye zorlamak için vardır. İşlemci, başka bir iş parçacığına verilecektir. Uygulamada, bir iş parçacığının sayacı okuyacağı an ile sayacı artıracağı an arasında kesintiye uğramayacağına dair hiçbir garanti yoktur. Sayaç değerinin okunması ile 1 artırılmış değerin yazılması arasındaki sürede işlemciyi kaybetme riski mevcuttur. Nitekim, artırma işlemi işlemci düzeyinde kesintiye uğrayabilecek birkaç temel komutun gerçekleştirilmesini gerektirir. Dolayısıyla, 1 saniyelik bekleme süresini içeren 2. adım, bu riski sistematik hale getirmek için vardır. Elde edilen sonuçlar şunlardır:
dos>thread3 5
A 05:44:34, le thread tache_0 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_1 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_2 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_3 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_4 a lu la valeur du compteur : 0
A 05:44:35, le thread tache_0 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_1 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_2 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_3 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_4 a écrit la valeur du compteur : 1
Nombre de threads générés : 1
Bu sonuçlara bakıldığında neler olduğu açıkça görülüyor:
- İlk iş parçacığı sayacı okur. 0 değerini bulur.
- 1 saniye durur ve böylece işlemciyi serbest bırakır
- ardından ikinci bir iş parçacığı işlemciyi devralır ve o da sayaç değerini okur. Önceki iş parçacığı henüz sayacı artırmadığı için değer hâlâ 0'dır. O da 1 saniye durur.
- 1 saniye içinde 5 iş parçacığının hepsi sırayla geçip 0 değerini okuma fırsatı bulur.
- Birbiri ardına uyanmaya başladıklarında, okudukları 0 değerini artırıp sayaca 1 değerini yazacaklar; bu durum ana program (Main) tarafından da doğrulanır.
Sorun nereden kaynaklanıyor? İkinci iş parçacığı, penceredeki sayacı güncelleme görevini tamamlamadan kesintiye uğrayan ilk iş parçacığı nedeniyle yanlış bir değer okudu. Bu da bizi bir programdaki kritik kaynak ve kritik bölüm kavramlarına götürür:
- kritik kaynak, aynı anda yalnızca bir iş parçacığı tarafından tutulabilen bir kaynaktır. Burada kritik kaynak, sayacıdır.
- Bir programın kritik bölümü, bir iş parçacığının yürütme akışında kritik bir kaynağa eriştiği komut dizisidir. Bu kritik bölüm sırasında kaynağa erişimin sadece o iş parçacığına ait olduğundan emin olunmalıdır.
8.5. Paylaşılan bir kaynağa özel erişim
Örneğimizde, kritik bölüm, sayacın okunması ile yeni değerinin yazılması arasında yer alan koddur:
' sayaç okuma
Dim valeur As Integer = cptrThreads
' bekleme
Thread.Sleep(1000)
' sayaç artışı
cptrThreads = valeur + 1
Bu kodu çalıştırmak için, bir iş parçacığının tek başına çalıştığından emin olunmalıdır. İş parçacığı kesintiye uğrayabilir, ancak bu kesinti sırasında başka bir iş parçacığının aynı kodu çalıştırmaması gerekir. .NET platformu, kodun kritik bölümlerine tekil erişimi sağlamak için çeşitli araçlar sunar. Mutex sınıfını kullanacağız:

Burada yalnızca aşağıdaki yapıcıları ve yöntemleri kullanacağız:
bir M senkronizasyon nesnesi oluşturur | |
T1 iş parçacığı, M.WaitOne() işlemini yürütürken M senkronizasyon nesnesinin mülkiyetini talep eder. Mutex M hiçbir iş parçacığı tarafından tutulmuyorsa (başlangıçta durum böyledir), bu Mutex, talep eden T1 iş parçacığına "verilir". Biraz sonra, T2 iş parçacığı aynı işlemi yaparsa, bu iş parçacığı bloke olur. Çünkü bir Mutex yalnızca bir iş parçacığına ait olabilir. T1 iş parçacığı, elinde tuttuğu Mutex M’yi serbest bıraktığında bu iş parçacığı engeli kalkacaktır. Böylece, Mutex M’yi bekleyen birden fazla iş parçacığı engellenebilir. | |
T1 iş parçacığı, M.ReleaseMutex() işlemini gerçekleştirirken M.Lorsque mutex'inin sahipliğini bırakır; T1 iş parçacığı işlemciyi kaybeder, sistem ise Mutex M'yi bekleyen iş parçacıklarından birine işlemciyi verebilir. Bunlardan sadece biri sırayla işlemciyi alacaktır; Mutex M'yi bekleyen diğer iş parçacıkları ise bloke kalacaktır |
Bir Mutex M, paylaşılan bir kaynak R'ye erişimi yönetir. Bir iş parçacığı, M.WaitOne() ile kaynak R'yi talep eder ve M.ReleaseMutex() ile geri verir. Bir seferde yalnızca tek bir iş parçacığı tarafından yürütülmesi gereken kritik bir kod bölümü, paylaşılan bir kaynaktır. Kritik bölümün yürütülmesinin senkronizasyonu şu şekilde yapılabilir:
burada M, bir Mutex nesnesidir. Elbette, başka bir iş parçacığının kritik bölüme girebilmesi için artık gereksiz hale gelen bir Mutex nesnesini serbest bırakmayı asla unutmamak gerekir; aksi takdirde, serbest bırakılmayan bir Mutex'i bekleyen iş parçacıkları işlemciye asla erişemeyecektir. Ayrıca, iki iş parçacığının birbirini beklediği kilitlenme durumu (deadlock) da önlenmelidir. Zaman içinde birbirini izleyen aşağıdaki eylemleri ele alalım:
- bir T1 iş parçacığı, M1 Mutex'inin sahipliğini alarak R1 paylaşımlı kaynağına erişim sağlar
- T2 iş parçacığı, R2 adlı paylaşılan kaynağa erişmek için M2 adlı Mutex'in sahipliğini alır
- T1 iş parçacığı, M2 Mutex'ini talep eder. Engellenir.
- T2 iş parçacığı, M1 Mutex'ini talep ediyor. İş parçacığı kilitlenmiş durumda.
Burada, T1 ve T2 iş parçacıkları birbirlerini bekliyor. Bu durum, iş parçacıklarının iki paylaşımlı kaynağa ihtiyaç duyması halinde ortaya çıkar: M1 Mutex'i tarafından kontrol edilen R1 kaynağı ve M2 Mutex'i tarafından kontrol edilen R2 kaynağı. Olası bir çözüm, tek bir Mutex (M) kullanarak her iki kaynağa aynı anda erişim talep etmektir. Ancak bu, örneğin pahalı bir kaynağın uzun süreli işgaline yol açıyorsa her zaman mümkün olmayabilir. Başka bir çözüm ise, M1'e sahip olan ve M2'i alamayan bir iş parçacığının, karşılıklı kilitlenmeyi önlemek için M1'i serbest bırakmasıdır. Az önce gördüklerimizi önceki örnekte uygulamaya koyarsak, uygulamamız şu şekilde olur:
' seçenekler
Option Explicit On
Option Strict On
' iş parçacığı kullanımı
Imports System
Imports System.Threading
Public Class thread4
' sınıf değişkenleri
Private Shared cptrThreads As Integer = 0 ' compteur de threads
Private Shared autorisation As Mutex
Public Overloads Shared Sub Main(ByVal args() As [String])
' kullanım kılavuzu
Const syntaxe As String = "pg nbThreads"
Const nbMaxThreads As Integer = 100
' argüman sayısının kontrolü
If args.Length <> 1 Then
' hata
Console.Error.WriteLine(syntaxe)
' durdurma
Environment.Exit(1)
End If
' argüman kalitesi denetimi
Dim nbThreads As Integer = 0
Try
nbThreads = Integer.Parse(args(0))
If nbThreads < 1 Or nbThreads > nbMaxThreads Then
Throw New Exception
End If
Catch
End Try
' kritik bölüme erişim yetkisinin başlatılması
autorisation = New Mutex
' iş parçacıklarının oluşturulması ve üretilmesi
Dim threads(nbThreads) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' oluşturma
threads(i) = New Thread(New ThreadStart(AddressOf incrémente))
' adlandırma
threads(i).Name = "tache_" & i
' başlatma
threads(i).Start()
Next i
' iş parçacıklarının bitmesini bekleme
For i = 0 To nbThreads - 1
threads(i).Join()
Next i
' sayaç görüntüleme
Console.Out.WriteLine(("Nombre de threads générés : " & cptrThreads))
End Sub
Public Shared Sub incrémente()
' iş parçacığı sayacını artırır
' kritik bölüme girme izni isteniyor
autorisation.WaitOne()
' sayaç okuma
Dim valeur As Integer = cptrThreads
' izleme
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads))
' bekleme
Thread.Sleep(1000)
' sayaç artırma
cptrThreads = valeur + 1
' takip
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a écrit la valeur du compteur : " & cptrThreads))
' erişim izni verilir
autorisation.ReleaseMutex()
End Sub
End Class
Elde edilen sonuçlar beklenenle uyumludur:
dos>thread4 5
A 05:51:10, le thread tache_0 a lu la valeur du compteur : 0
A 05:51:11, le thread tache_0 a écrit la valeur du compteur : 1
A 05:51:11, le thread tache_1 a lu la valeur du compteur : 1
A 05:51:12, le thread tache_1 a écrit la valeur du compteur : 2
A 05:51:12, le thread tache_2 a lu la valeur du compteur : 2
A 05:51:13, le thread tache_2 a écrit la valeur du compteur : 3
A 05:51:13, le thread tache_3 a lu la valeur du compteur : 3
A 05:51:14, le thread tache_3 a écrit la valeur du compteur : 4
A 05:51:14, le thread tache_4 a lu la valeur du compteur : 4
A 05:51:15, le thread tache_4 a écrit la valeur du compteur : 5
Nombre de threads générés : 5
8.6. Olaylara dayalı senkronizasyon
Bazen üretici-tüketici durumu olarak da adlandırılan aşağıdaki durumu ele alalım.
- Elimizde, bazı süreçlerin veri kaydettiği (üreticiler) ve diğerlerinin bu verileri okuduğu (tüketiciler) bir tablo var.
- Üreticiler birbirine eşittir ancak birbirini dışlar: Her seferinde yalnızca bir üretici verilerini tabloya kaydedebilir.
- Tüketiciler birbirine eşittir ancak birbirini dışlar: bir seferde yalnızca bir okuyucu tabloya yerleştirilen verileri okuyabilir.
- Bir tüketici, ancak bir üretici tabloya veri eklediğinde verileri okuyabilir ve bir üretici, tablodaki veriler tüketildiğinde yeni verileri tabloya ekleyebilir.
Bu açıklamada iki paylaşılan kaynak ayırt edilebilir:
- yazma yetkisi olan tablo
- okunur tablo
Bu iki paylaşımlı kaynağa erişim, daha önce görüldüğü gibi her kaynak için birer tane olmak üzere Mutex'ler aracılığıyla kontrol edilebilir. Bir tüketici okuma tablosunu elde ettikten sonra, içinde veri olup olmadığını kontrol etmelidir. Bunu bildirmek için bir olay kullanılacaktır. Benzer şekilde, yazma tablosunu elde eden bir üretici, bir tüketicinin tabloyu boşaltmasını beklemelidir. Burada da yine bir olay kullanılacaktır.
Kullanılacak olaylar, AutoResetEvent sınıfının bir parçası olacaktır:

Bu tür bir olay, bir boole değerine benzer ancak aktif veya yarı aktif beklemeleri önler. Dolayısıyla, yazma hakkı bir peutEcrire boole değeri ile kontrol ediliyorsa, bir üretici yazma işleminden önce şu tür bir kod çalıştıracaktır:
veya
İlk yöntemde, iş parçacığı işlemciyi gereksiz yere meşgul eder. İkinci yöntemde ise, her 100 ms'de bir peutEcrire boole değişkeninin durumunu kontrol eder. AutoResetEvent sınıfı, durumu daha da iyileştirir: iş parçacığı, beklediği olay gerçekleştiğinde uyandırılmasını talep eder:
AutoEvent peutEcrire=new AutoResetEvent(false) ' peutEcrire=false;
....
peutEcrire.WaitOne() ' le thread attend que l'évt peutEcrire passe à vrai
İşlem
, peutEcrire adlı boole değerini false olarak başlatır.
işlemi, bir iş parçacığı tarafından yürütüldüğünde, peutEcrire boole değeri doğruysa iş parçacığının ilerlemesini sağlar; aksi takdirde, bu değer doğru olana kadar iş parçacığı bloke olur. Başka bir iş parçacığı, peutEcrire.Set() işlemiyle bu değeri doğru olarak, ya da peutEcrire.Reset() işlemiyle yanlış olarak değiştirecektir.
Üretici-tüketici programı şöyledir:
' okuma ve yazma iş parçacıklarının kullanımı
' paylaşılan kaynakların ve senkronizasyonun eşzamanlı kullanımını gösterir
' seçenekler
Option Explicit On
Option Strict On
' iş parçacıklarının kullanımı
Imports System
Imports System.Threading
Public Class lececr
' sınıf değişkenleri
Private Shared data(5) As Integer ' ressource partagée entre threads lecteur et threads écrivain
Private Shared lecteur As Mutex ' variable de synchronisation pour lire le tableau
Private Shared écrivain As Mutex ' variable de synchronisation pour écrire dans le tableau
Private Shared objRandom As New Random(DateTime.Now.Second) ' un générateur de nombres aléatoires
Private Shared peutLire As AutoResetEvent ' signale qu'on peut lire le contenu de data
Private Shared peutEcrire As AutoResetEvent
Public Shared Sub Main(ByVal args() As [String])
' oluşturulacak iş parçacığı sayısı
Const nbThreads As Integer = 3
' bayrakların başlatılması
peutLire = New AutoResetEvent(False) ' on ne peut pas encore lire
peutEcrire = New AutoResetEvent(True) ' on peut déjà écrire
' senkronizasyon değişkenlerinin başlatılması
lecteur = New Mutex ' synchronise les lecteurs
écrivain = New Mutex ' synchronise les écrivains
' okuma iş parçacıklarının oluşturulması
Dim lecteurs(nbThreads) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' oluşturma
lecteurs(i) = New Thread(New ThreadStart(AddressOf lire))
lecteurs(i).Name = "lecteur_" & i
' başlatma
lecteurs(i).Start()
Next i
' yazma iş parçacıklarının oluşturulması
Dim écrivains(nbThreads) As Thread
For i = 0 To nbThreads - 1
' oluşturma
écrivains(i) = New Thread(New ThreadStart(AddressOf écrire))
écrivains(i).Name = "écrivain_" & i
' başlatma
écrivains(i).Start()
Next i
'elin sonu
Console.Out.WriteLine("fin de Main...")
End Sub
' tablonun içeriğini okuma
Public Shared Sub lire()
' kritik bölüm
lecteur.WaitOne() ' un seul lecteur peut passer
peutLire.WaitOne() ' on doit pouvoir lire
' tablo okuma
Dim i As Integer
For i = 0 To data.Length - 1
'1 saniye bekleme
Thread.Sleep(1000)
' görüntüleme
Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : Le lecteur " & Thread.CurrentThread.Name & " a lu le nombre " & data(i)))
Next i
' artık okunamıyor
peutLire.Reset()
' yazılabilir
peutEcrire.Set()
' kritik bölüm sonu
lecteur.ReleaseMutex()
End Sub
' tabloya yazma
Public Shared Sub écrire()
' kritik bölüm
' sadece bir yazar geçebilir
écrivain.WaitOne()
' yazma izni beklenmelidir
peutEcrire.WaitOne()
' tabloya yazma
Dim i As Integer
For i = 0 To data.Length - 1
'1 saniye bekleme
Thread.Sleep(1000)
' görüntüleme
data(i) = objRandom.Next(0, 1000)
Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : L'écrivain " & Thread.CurrentThread.Name & " a écrit le nombre " & data(i)))
Next i
' artık yazma yapılamaz
peutEcrire.Reset()
' okuma yapılabilir
peutLire.Set()
'kritik bölüm sonu
écrivain.ReleaseMutex()
End Sub
End Class
Çalıştırıldığında şu sonuçlar elde edilir:
dos>lececr
fin de Main...
05:56:56 : L'écrivain écrivain_0 a écrit le nombre 459
05:56:57 : L'écrivain écrivain_0 a écrit le nombre 955
05:56:58 : L'écrivain écrivain_0 a écrit le nombre 212
05:56:59 : L'écrivain écrivain_0 a écrit le nombre 297
05:57:00 : L'écrivain écrivain_0 a écrit le nombre 37
05:57:01 : L'écrivain écrivain_0 a écrit le nombre 623
05:57:02 : Le lecteur lecteur_0 a lu le nombre 459
05:57:03 : Le lecteur lecteur_0 a lu le nombre 955
05:57:04 : Le lecteur lecteur_0 a lu le nombre 212
05:57:05 : Le lecteur lecteur_0 a lu le nombre 297
05:57:06 : Le lecteur lecteur_0 a lu le nombre 37
05:57:07 : Le lecteur lecteur_0 a lu le nombre 623
05:57:08 : L'écrivain écrivain_1 a écrit le nombre 549
05:57:09 : L'écrivain écrivain_1 a écrit le nombre 34
05:57:10 : L'écrivain écrivain_1 a écrit le nombre 781
05:57:11 : L'écrivain écrivain_1 a écrit le nombre 555
05:57:12 : L'écrivain écrivain_1 a écrit le nombre 812
05:57:13 : L'écrivain écrivain_1 a écrit le nombre 406
05:57:14 : Le lecteur lecteur_1 a lu le nombre 549
05:57:15 : Le lecteur lecteur_1 a lu le nombre 34
05:57:16 : Le lecteur lecteur_1 a lu le nombre 781
05:57:17 : Le lecteur lecteur_1 a lu le nombre 555
05:57:18 : Le lecteur lecteur_1 a lu le nombre 812
05:57:19 : Le lecteur lecteur_1 a lu le nombre 406
05:57:20 : L'écrivain écrivain_2 a écrit le nombre 442
05:57:21 : L'écrivain écrivain_2 a écrit le nombre 83
^C
Aşağıdaki noktalara dikkat çekilebilir:
- Kritik bölümde işlemciyi kaybetmesine rağmen, her seferinde gerçekten de yalnızca 1 okuyucu bulunmaktadır: lire
- Aynı anda yalnızca bir yazıcı vardır, ancak bu yazıcı kritik bölüm écrire'te işlemciyi kaybeder
- bir okuyucu, tabloda okunacak bir şey olduğunda okur
- bir yazıcı, tablo tamamen okunduktan sonra yazmaya başlar

