Makale Özeti

Monitor kullanarak thread senkronizasyonu sağlamak

Makale


C# ile Thread Senkronizasyonu:
 

Önceki yazılarda gördüğümüz gibi C#’ta kolaylıkla threadler yaratıp uygulamamızın farklı görevleri eşzamanlı olarak yapmasını sağlayabiliyorduk. Birbirinden bağımsız işler yapan ve ayrı değişkenleri ve kaynakları kullanan threadleri çalıştırmak oldukça kolaydır. Fakat threadler kaynakları(değişkenleri, nesneleri) paylaşmaya basladıkça threadleri yönetmek zorlaşır. Paylaşılan kaynaklarda, gerek kaynağın aynı anda sadece bir threade hizmet verebilecek olmasından (Dosya erisimi gibi), gerekse kullanılan değişkenlerin değerlerinde oluşabilecek hatalardan dolayı, threadlerin çalışmasının senkronize edilmesi gerekir.
 

Örnek vermek gerekirse, iki threadimiz bir hesaptakiTutar adlı bir değişkeni paylaşıyor, biri üzerinde artırma, biri azaltma yapıyor olsun. Birinci threadimiz bunun değerini okusun, sonra işlemini yapsın, sonra da artan yeni değeri yerine yazsın. İkinci threadimiz de benzer bir mantıkla bu değişkeni okusun, işlemini yapsın ve azalmış değeri yerine yazsın.
 

Şimdi ilk threadimiz değeri okumuş ve işlemi yaparken bu threadin durdurulduğunu, ve diğer threade sıra verildiğini düşünelim. Bu anda henüz yenilenmemiş değeri okuyan ikinci thread işini bitirip kendi değerini değişkene yazsın, sonra sıra tekrar birinci threade geçsin ve o da hesaptaki tutarın değiştiğinden habersiz kendi değerini hesaptakiTutar değişkenine yazsın. Sonuç tabii ki doğru olmayacaktır.

 

Bu şekilde paylaşılan kaynakların önüne geçmek için C# bize bazı altyapılar sunar. Bunlar Interlock sınıfı, lock kelimeciği, ve Monitor sınıfıdır.

 

Şimdi bu yapılardan en gelişmişi olan Monitor sınıfının kullanımına göz atalım.
 

Monitor.Enter(nesneIsmi):


Monitor sınıfının Enter metodunu kullanarak, programımızın belirli bir nesne üzerinde güvenli bir şekilde calışmaya başlamasını sağlayabiliriz. Güvenli kelimesinden kastımız, ancak o nesneyi tek kullanan bizim o anki threadimiz ise, yani o anda başka bir thread o nesneyi kullanmıyorsa, thread calışmaya devam edecektir. Aksi halde thread o nesne müsait olana kadar beklemede kalacaktır.
 

Monitor.Wait(nesneIsmi):
 

Eğer threadimizin, programımızda oluşan bir durum sonucunda beklemeye almak istersek, Wait metodunu kullanabiliriz. Artık threadimiz Wait metoduna parametre olarak geçtiğimiz nesne üzerinde beklemeye başlayacak, ve sinyal gelene kadar da beklemeye devam edecektir.
 

Monitor.Exit(nesneIsmi):
 

Enterla bir nesne üzerinde calışmaya başladıktan sonra nesneyle işimiz bittiğinde, Monitor.Exit metodunu kullanarak o nesneyi artık kullanmıyor olduğumuzu belirtmeliyiz ki, o nesneyi kullanacak olan başka bir thread, Monitor.Enter metodunu cağırdığında beklemede kalmasın.
 

Monitor.Pulse(nesneIsmi):
 

Monitor.Pulse metodu ise bir threadin başka threadlere sinyal vermesini sağlar. Çalışan threadimiz o an başka threadlerden birinin artık çalışmaya başlayabileceğini düşünüyorsa, bu metodu cağırarak, sistemin o nesne üzerinde bekleyen threadlerden birini artık beklemeden çıkarıp çalıştırmasını sağlar.

 

Şimdi Monitor kullanarak threadlerimizi senkronize ettiğimiz, genel bir örneğe bakalım. Örneğimizde iki threadimiz olsun, bir threadimiz bir integer değeri yükseltirken(üretirken), diğer threadimiz bu değeri azaltmakla(tuketmekle) görevli olsun.
Bir gereksinim olarak da, üretici belirli bir sayıya ulaşmadan da tüketici çalışmaya başlayamasın.

Programın kodu şöyle: 

 

using System;
using System.IO;
using System.Threading; 

namespace ThreadMonitor

{

      class Sinifim

      {

            private int bekleyen = 0;      

            static void Main(string[] args)

            {

                  Sinifim s = new Sinifim();

                  s.ThreadleriCalistir();

            }

            public void ThreadleriCalistir(){ 

                  // nesnelerimizi olusturalim

                  Thread uretici = new Thread(new ThreadStart(Uret));

                  Thread tuketici = new Thread(new ThreadStart(Tuket)); 

                  // threadlerimiz calismaya baslasin

                  tuketici.Start();

                  uretici.Start();

                  // threadlerimizin calismasi bitmeden cikmayalim

                  tuketici.Join();

                  uretici.Join();

            }

            public void Uret(){

                  try

                  {

                       Monitor.Enter(this);

                        // artik monitore girdim, bekleyen degiskenine benden baskasi erisemez..

                        while(bekleyen<20){

                              // uretelim

                              bekleyen++;

                              Thread.Sleep(50);

                              Console.WriteLine("Uretici: Urettim " + bekleyen.ToString());

                        }

                        // 20 tane urettim

                        Console.WriteLine("Uretici: Uretecegim kadar urettim, simdi haber vereyim");

                        // Pulse metodunu çağırdığımda eğer this nesnesi üzerinde bekleyen threadlerden biri beklemekten kurtulacaktır

                        Monitor.Pulse(this);

                  }

                  finally

                  {

                        Console.WriteLine("Uretici: isim bitti, cikiyorum");

                        // her kosulda Monitorden cikmaliyiz

                        Monitor.Exit(this);

                        // artik monitorden ciktim

                        // baska threadler degiskenleri kullanabilir

                  }

 

            }

            public void Tuket(){

                  try

                  {

                        // eger monitorde baskasi varsa

                        // ben burada bekleyecegim

                        Monitor.Enter(this);

                        if(bekleyen<20)

                        {

                              Console.WriteLine("Tuketici: Bekleyen sayisi yetersiz bekliyorum");

                              // kendim karar aldim, bekleyecegim

                              Monitor.Wait(this);

                        }

                        while(bekleyen>0){

                       

                              // tuketelim

                              Console.WriteLine("Tuketici: Tukettim "+bekleyen.ToString());

                              bekleyen--;

                              // biraz bekleyelim

                              // tuketim islemi..

                              Thread.Sleep(50);

                        }

                  }

                  finally{

                        Console.WriteLine("Tuketici: isim bitti, cikiyorum");

                        // her kosulda Monitorden cikmaliyiz

                        Monitor.Exit(this);

                  }

            }

      }

}

 


Şimdi programımızın çıktısını inceleyelim.
Tuketici: Bekleyen sayisi yetersiz bekliyorum
Uretici: Urettim 1
Uretici: Urettim 2
Uretici: Urettim 3
Uretici: Urettim 4
Uretici: Urettim 5
Uretici: Urettim 6
Uretici: Urettim 7
Uretici: Urettim 8
Uretici: Urettim 9
Uretici: Urettim 10
Uretici: Urettim 11
Uretici: Urettim 12
Uretici: Urettim 13
Uretici: Urettim 14
Uretici: Urettim 15
Uretici: Urettim 16
Uretici: Urettim 17
Uretici: Urettim 18
Uretici: Urettim 19
Uretici: Urettim 20
Uretici: Uretecegim kadar urettim, simdi haber vereyim
Uretici: isim bitti, cikiyorum
Tuketici: Tukettim 20
Tuketici: Tukettim 19
Tuketici: Tukettim 18
Tuketici: Tukettim 17
Tuketici: Tukettim 16
Tuketici: Tukettim 15
Tuketici: Tukettim 14
Tuketici: Tukettim 13
Tuketici: Tukettim 12
Tuketici: Tukettim 11
Tuketici: Tukettim 10
Tuketici: Tukettim 9
Tuketici: Tukettim 8
Tuketici: Tukettim 7
Tuketici: Tukettim 6
Tuketici: Tukettim 5
Tuketici: Tukettim 4
Tuketici: Tukettim 3
Tuketici: Tukettim 2
Tuketici: Tukettim 1
Tuketici: isim bitti, cikiyorum

Önce tüketici çalışmaya başlamış, fakat üretileni yeterli görmemiş ve beklemeye geçmiş. 
Tüketici beklemeye geçince Monitorde bekleyen üretici çalışmaya başlayabilmiş, 
üreteceğini üretmiş ve sinyal vererek  monitorde bekleyen tüketiciyi tekrar harekete geçirmiş.

 

Bu makalede threadlerin beraber sorunsuz bir şekilde çalışması için,Monitor kullanarak, kaynaklara nasıl teker teker girmelerinin sağlanabileceğini, ve gerektiğinde nasıl başka bir threade sinyal göndererek, o threadin çalışmaya başlamasını sağlayabileceğini gördük. Bu bilgilerle artık yarattığımız threadlerin daha sorunsuz ve senkronize çalışmasını sağlayabiliriz.
 
İyi çalışmalar,

Onur Ağın
onur@onura.net