|
C# ile Çok Kanallı (Multithread) Programlamada Senkronizasyon |
|
Gönderiliyor lütfen bekleyin... |
|
|
Windows işletim sisiteminin önemli özelliklerinden biri de multithreaded bir
mimariye sahip olmasıdır. Peki multithreaded (çok kanallı) program ne demektir ?
Genellikle proses ve thread kavramları birbirine karıştırılır. Derlenip
çalıştırılan bir programa işletim sistemi içersinde yer alan bir proses denir.
Örneğin bir hesap makinası programı ile resim programını ard arda çalıştıralım.
Bu iki program işletim sistemi tarafından iki ayrı proses olarak ele alınır. Bu
iki program birbirlerinin yaptığı işlemlere müdahale etmez. (İstersek prosesleri
birbirlerine müdahale edecek şekilde programlayabiliriz. Genellikle bu istisnayi
bir durumdur.) Her proses kendi bellek alanı içersinde koşmaya devam eder.
Thread ise bir proses içersinde yer alan ve işlem yapan bir rutindir. Bir proses
içersinde birden fazla thread var olabilir. Threadler genellikle bir proses
içersinde ayrı kanallarda paralel olarak çalışması istenen metodların
yürütülmesinde kullanılır. Bazı thread programlama örnekleri için şu linlerkden
faydalanabilirsiniz:
Çok Kanallı(Multithread) Uygulamalar,
Thread’leri Belli Süreler Boyunca Uyutmak ve Yoketmek,
Thread’lerde Öncelik(Priority) Durumları.
Threadler sayesinde ayrı metodları aynı anda çalıştırabiliriz. İşletim sistemi
her threade çalışması için belli bir zaman aralığı verir. Bu zaman aralığı
dolduğunda çalışan threadden çıkılıp, program içersindeki diğer bir metoda veya
başka bir threade girilir. Bir metod içersinde yapılan iş ne kadar uzun
sürüyorsa o metodun bağlı olduğu threadde o kadar fazla zaman harcanır. Bazen
bu süre o kadar fazla olur ki, proses içersindeki başka bir threade çalışması
için çok az zaman kalır. Başlatılan threadlerin aynı anda senkron bir şekilde
çalışabilmesi için .NETte senkronizasyon teknikleri kullanılır. Burada aynı
andalığı (senkronizasyon) biraz açmamız gerekebilir. Örneğin üç tane kanalımız
(thread) olsun. Her bir kanalda bir progressbar dolduruluyor olsun.
public void DegerArttir1()
{
for (int i = 1; i <= 100; ++i)
{
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
}
}
public void DegerArttir2()
{
for (int j = 1; j <= 100; ++j)
{
progressBar2.Value += 1;
lbThreads.Items.Add("Thread 2");
Thread.Sleep(100);
}
}
public void DegerArttir3()
{
for (int k = 1; k <= 100; ++k)
{
progressBar3.Value += 1;
lbThreads.Items.Add("Thread 3");
Thread.Sleep(150);
}
}
private void btnStart_Click(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(DegerArttir1));
th2 = new Thread(new ThreadStart(DegerArttir2));
th3 = new Thread(new ThreadStart(DegerArttir3));
th1.Start();
th2.Start();
th3.Start();
}
|
Üç kanal
çalıştırıldığında ekran çıktısı aşağıdaki gibi olacaktır. Burada dikkat
ederseniz ilk kanalın bağlı olduğu DegerArttir1 metodunda 10 milisaniyelik bir
bekleme süresi varken ( Thread.Sleep(10) ), üçüncü kanalın bağlı olduğu
DegerArttir3 metodunda 150 milisaniyelik bir bekleme süresi vardır. Süreler
arasındaki bu farktan dolayı her kanala ayrılan zaman birbirinden farklıdır.
Burada öncelikli kanal th1’dir. Bu yüzden birinci progressbar diğerlerinden önce
dolar.
Örnek uygulamayı indirmek için
tıklayınız.
Şekil 1.
Asenkron kanallarla
doldurulan üç progressbar.
Aynı anda çalışan
kanallar (senkron threadler) ise birbirlerinin metotlarının işlemlerinin
bitmesini bekler. Bir kanal diğer bir kanaldaki işlem bitmeden harekete geçmez.
Bu sayede üç kanal senkron bir şekilde çalışır. .NET’te thread senkronizasyonu
için dört yöntem kullanılabilir.
Şekil 2.
.NET’te thread
senkronizasyon teknikleri.
Interlocked :
Interlocked
sınıfı birden fazla kanal tarafından kullanılan değişkenler üzerinde çeşitli
işlemlerin yapılmasına olanak sağlayan bir sınıftır. Buradaki değişken değer
tipinde olabileceği gibi herhangi bir nesne de olabilir. Interlocked
sınıfının şimdilik sadece Exchange metodu referans tipleri ile çalışabilir.
Exchange’in generic metot olarak tasarlanmış bir overload’u da mevcuttur.
Increment, decrement metodları ise Int32, Int64 tipi değişkenleri parametre
olarak kabul eder. Interlocked sınıfını şöyle kullanabiliriz:
public void DegerArttirInterlocked1()
{
try
{
while (counter <= 300)
{
Interlocked.Increment(ref counter);
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(80);
}
}
catch { }
} |
Burada counter global
olarak tanımlanmış olan int değişkendir. Increment fonksiyonu ile counter
değişkeni kontrollü arttırılmaktadır. Bir thread altında counter üzerinde işlem
yapılırken diğer bir thread içersinde counter üzerinde aynı anda işlem yapılmaz.
Lock
:
Bir kanal içersindeki işlemlerin, diğer bir kanal tarafından müdahale edilmeden
çalışabilmesi için lock anahtar kelimesi kullanılır. Lock ile
bloklanmış olan işlemler bir kanal içersinde tamamlanıncaya kadar çalışırlar. Bu
süre zarfında başka kanallar lock ile bloklanmış işlemlerin bitmesini
beklerler. İşlem bitince diğer kanallar kendi metodlarını çalıştırmaya devam
eder. Bu şekilde senkronizasyon sağlanabilir. Lock parametre olarak
herhangi bir nesne alabilir. Örneğimizde form nesnemizi parametre olarak
kullandık.
public void DegerArttirLock1()
{
for (int i = 1; i <= 100; ++i)
{
lock (this)
{
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
}
}
} |
Genellikle lock’a
aktarılan parametrenin public olmamasına dikkat edilir. Lock ile
kilitlenen nesne public ise bu nesne üzerinde işlem yapmak isteyen metodlar (bir
thread’e bağlı olmayan) da kilitlenir. Bu tür kilitlenmelere deadlocks (kısır
döngü) denir. Bu istenmeyen bir durumdur. Public yerine protected veya private
kullanmak bu sorunu çözer. Şekil 3’de DegerArttirLock metodlarını çağıran
kanallar çalıştırıldığında progressbar’ların ve kanalların durumu
sergilenmiştir. Dikkate edilirse kanallar 123, 123 şeklinde art arda düzgün bir
şekilde çağırılmaktadır. Asenkron kanallarda ise 132, 131, 111 şeklinde düzensiz
çağırılmalar gerçekleşir.
Şekil 3.
Senkron kanallarla
doldurulan üç progressbar.
Monitor :
Monitor,
nesnelere kanalların senkronize bir şekilde ulaşmasını sağlayan bir sınıftır.
Monitor sınıfı referans tipindeki değişkenleri senkronize etmek için
kullanılır. Değer tipindeki değişkenler için
monitor sınıfı kullanılmaz. Monitor’ün kullanımı lock’un kullanımına
benzer. Monitor blokladığı kodların başka kanallar tarafından
erişilmesini engeller. Monitor sınıfının bazı metodları aşağıdaki tabloda
özetlenmiştir.
Metod |
Görevi |
Enter, TryEnter |
Bloklanmak
istenen nesne Enter ile Monitor nesnesine kayıt ettirilir.
Diğer kanallar bu bloklanan nesneye müdahale edemez. Nesne artık
kilitlenmiştir.
Monitor.Enter ile bloklanmak istenen kodlar için lock’a benzer şekilde
başlangıç verilebilir. |
Wait |
Bloklanmış
nesne üzerindeki kilidi açar. Diğer kanallar nesneye ulaşabilir ve onu
kilitleyebilir. |
Pulse, PulseAll |
Bir kanaldaki
kilitlenmiş nesnenin, üzerindeki kilidin kaldırılacağını bekleyen
kanallara duyurmak için kullanılır. Diğer kanallar kendi kuyruklarına
kilidi açılacak olan nesneyi kayıt ederler. Pulse, Exit metodu öncesi
kullanılan bir metottur ve ana amacı diğer kanallara kilidin açılacağını
önceden duyurmaktır. |
Exit |
Nesne
üzerindeki kilidi açar.
Monitor.Exit ile bloklanmak istenen kodlar için lock’a benzer şekilde
sonlanma noktası verilebilir. |
Tablo 1.
Monitor sınıfının bazı
metodları.
Monitor
sınıfının DegerArttir fonksiyonunda nasıl kullanılacağı aşağıda gösterilmiştir.
Kısır döngüler (deadlock) ile karşılaşmamak için monitorObject nesnesinin
üzerindeki kilit finally bloğunda herzaman kaldırılmaktadır.
private object monitorObject = new object();
public void DegerArttirMonitor1()
{
for (int i = 1; i <= 100; ++i)
{
try
{
Monitor.Enter(monitorObject);
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);
Monitor.Pulse(monitorObject);
}
finally
{
Monitor.Exit(monitorObject);
}
}
} |
Mutex
:
Mutex
sınıfı monitor sınıfına benzer ancak System.Threading.WaitHandle sınıfından
türetilmiştir ve türediğin sınıfın daha kolay kullanılabilir bir
genelleştirilmesidir. Mutex sınıfı kanallar tarafından ortak kullanılan
nesnelere aynı anda ulaşılıp, işlem yapılmasını engellemek için kullanılır.
Mutex sınıfı ortak kullanılan kaynaklara bir t zamanında sadece bir kanalın
ulaşabilmesini garanti eder. Mutex kendisini kullanan kanalın tekilliğini
(identity) kontrol eder. Bir mutex’e sahip olan kanal WaitOne metodu ile onu
kilitler ve ReleaseMutex metodu ile mutex’i serbest bırakır. Bir mutex
kullanan kanal sadece kendi mutex’ini ReleaseMutex metodu ile açabilir. Kanallar
birbirlerinin mutex’lerini serbest bırakamaz. Bizim örneğimizde
progressbar’ların tam anlamı ile aynı anda ilerlemediğini fark edeceksiniz.
Burada mutex’i sadece ortak kaynaklara güvenli erişim amacı ile kullandık.
private object mutexObject = new object();
public void DegerArttirMutex1()
{
for (int i = 1; i <= 100; ++i)
{
mutexObject.WaitOne();
progressBar1.Value += 1;
lbThreads.Items.Add(>"Thread 1");
Thread.Sleep(10);
mutexObject.ReleaseMutex();
}
} |
Semaphore :
Semaphore
sınıfı .NET 2.0 framework ile gelen yeni bir sınıftır. Bu sınıf da
System.Threading.WaitHandle sınıfından türetilmiştir. Semaphore sınıfının
Mutex sınıfından farkı, farklı kanalların birbirlerinin Semaphore’larının
kilitlerini Release metodu ile açabilmeleridir. Bir kanal semaphore’un WaitOne
metodunu birçok kez çağırabilir. Bu kilitleri açmak için art arda Release
metodunu çağırabileceği gibi, Release(int) overload’unu da kullanabilir.
Semaphore kendisini kullanan kanalın identity’sine bakmaz. Bu yüzden farklı
kanallar birbirlerinin semaphore’larının WaitOne ve Release metodlarını
çağırabilir. Herbir WaitOne metodu çağırıldığında semaphore’un sayacı bir
azaltılır. Herbir release metodu çağırıldığında ise sayaç bir arttırılır.
Semaphore’un yapılandırıcısında (constructor) sayacın minimum ve maksimum
değerleri belirlenebilir.
private Semaphore semaphoreObject = new Semaphore(0,2);
public void DegerArttirSemaphore1()
{
for (int i = 1; i <= 100; ++i)
{
semaphoreObject.WaitOne();
progressBar1.Value += 1;
lbThreads.Items.Add("Thread 1");
Thread.Sleep(10);s
semaphoreObject.Release();
}
}
private void btnSemaphore_Click(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(DegerArttirSemaphore1));
th2 = new Thread(new ThreadStart(DegerArttirSemaphore2));
th3 = new Thread(new ThreadStart(DegerArttirSemaphore3));
th1.Start();
th2.Start();
th3.Start();
semaphoreObject.Release(2);
} |
Sonuç
:
Bu makalede threadlerin senkronizasyonunu ve threadler tarafından ortak olarak
kullanılan kaynakların nasıl yönetilebileceğini inceledik. Çok kanallı
programlamada karşılaşılan en büyük sorun kısırdöngü (deadlock) denilen
programın kilitlenmesine yol açan kodlardır. Kısır döngülerden kurtulmak için
ortak kullanılan değişkenler ve nesneler yukarıda anlatılan teknikler ile izole
edilmelidir. Böylece bir proses içersinde birçok kanalda birçok metodu paralel
bir şekilde çalıştırabiliriz.Ferhat
Nutku
[email protected]
Makale:
C# ile Çok Kanallı (Multithread) Programlamada Senkronizasyon C#, Visual C# ve .NET Ferhat Nutku
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|