Senkron & Asenkron ve MultiThread Programlama -4 — Non-Blocking Syncronization & Tools

Alperen Öz
11 min readMay 26, 2024

--

Race Condition & Senkronizasyon Teknikleri -3

Non-Blocking Syncronization & Tools

Photo by Austin Distel on Unsplash

Non-Blocking Synchronization

  • Paralel programlama ve çoklu iş parçacığı uygulamaları geliştirirken, senkronizasyon teknikleri hayati önem taşır. Bu şimdiye kadar bahsettiğimiz teknikler de kendi arasında (bloklayıcı) blocking ve bloklayıcı olmayan (non-blocking) senkronizasyon yöntemleri, iş parçacıklarının kaynaklara erişimini düzenleme ve senkronize etme açısından farklı yaklaşımlar sunar.
  • Bu iki tür senkronizasyon, iş parçacıklarının etkileşimi ve program akışı üzerinde farklı etkiler yaratabilir.

Blocking Senkronizasyon:

Blocking senkronizasyon, bir iş parçacığının bir kaynağa erişene kadar diğer iş parçacıklarının bekleyebileceği, yani bloklanacağı anlamına gelir. Bu, kritik bölümler, mutex’ler ve semaphore’lar gibi mekanizmalar kullanılarak gerçekleştirilir.

Avantajlar:

  • Güvenlik: Kilit bölümler gibi mekanizmalar, birden fazla iş parçacığının aynı anda kritik bir bölüme girmesini önleyerek veri bütünlüğünü ve tutarlılığını garanti eder. Bu, bankacılık uygulamaları gibi para işlemleriyle çalışan programlar için kritik öneme sahiptir.
  • Basitlik: Mutex’ler ve semaphore’lar gibi mekanizmalar nispeten basit ve anlaşılır. Bu, bu mekanizmaları kullanmayı öğrenmeyi ve kodlarınıza dahil etmeyi kolaylaştırır.
  • Tahmin edilebilirlik: Blocking senkronizasyon, bir iş parçacığının bir kaynağa erişmeden önce ne kadar süre bekleyeceğini tahmin etmeyi kolaylaştırır. Bu, program akışını ve performansı daha iyi planlamanıza yardımcı olabilir.

Dezavantajlar:

  • Performans: Bir iş parçacığı bir kaynağa erişmeyi beklerken “boşta” kalır yani bloklanır. ve bu da genel program performansını düşürebilir. Yoğun iş parçacığı rekabeti olan programlarda bu önemli bir sorun olabilir.
  • Kilitlenme (DeadLock): Belirli durumlarda, iki veya daha fazla iş parçacığı birbirini sonsuza dek bekleyebilir, bu da kilitlenme olarak bilinen bir duruma yol açabilir. Kilitlenmeler, programın çökmesine veya beklenmedik davranışa neden olabilir.
  • Ölçeklenebilirlik: Blocking senkronizasyon mekanizmaları, iş parçacığı sayısı arttıkça ölçeklenmede zorlanabilir. Büyük ve karmaşık çok iş parçacıklı uygulamalarda bu bir sorun olabilir.
  • Uyku ve Uyanma Maliyeti: Thread’lerin kilitleme nedeniyle beklemesi, işletim sistemi düzeyinde işlemi uyandırma maliyeti ile birlikte gelmektedir. Bu durumda da işletim sistemi tarafından thread yönetimi için ek kaynak kullanımı söz konusu olacak, bu da maliyet olarak yansıyacaktır.

Non-Blocking Senkronizasyon:

Non-blocking, bir işlem veya iş parçacığının bir kaynağa erişmeye çalışırken beklememesi veya bloke olmaması anlamına gelir. Non-blocking senkronizasyon teknikleri, bir iş parçacığının kritik bir bölgeye erişim için beklemesini gerektirmez. Bunun yerine, iş parçacığı başka bir işlemi gerçekleştirir veya başka bir iş parçacığı tarafından kullanılan kaynağa alternatif bir işlem yapar.

Avantajlar:

  • Performans: Non-blocking senkronizasyon mekanizmaları, bekleyen iş parçacıkları olmadığı için program performansını önemli ölçüde artırabilir. Bu, yüksek performanslı ve yanıt süresinin kritik önem taşıdığı uygulamalar için idealdir.
  • Kullanılabilirlik: Non-Blocking tekniği, blocking tekniklerine nazaran kot karmaşası daha da azdır. Böylece kod bakımı ve kullanımı daha kolay hale gelecektir.
  • Kilitlenme riski: Non-blocking senkronizasyon, kilitlenmelere karşı daha az duyarlıdır. Bu, programın daha güvenilir ve tahmin edilebilir olmasını sağlar.
  • Ölçeklenebilirlik: Non-blocking senkronizasyon mekanizmaları, iş parçacığı sayısı arttıkça daha iyi ölçeklenebilir. Bu, büyük ve karmaşık çok iş parçacıklı uygulamalar için idealdir.

Dezavantajlar:

  • Uygulama Karmaşıklığı: Non-blocking senkronizasyon mekanizmaları, blocking senkronizasyona kıyasla her ne kadar kod karmaşıklığını azaltsa da, daha karmaşık ve anlaşılması zor bir uygulama ortaya koyabilir. Bu, bu mekanizmaları kullanmayı öğrenmeyi ve kodlarınıza doğru şekilde dahil etmeyi zorlaştırabilir.
  • Güvenlik: Bazı non-blocking senkronizasyon mekanizmaları, belirli durumlarda veri bütünlüğü veya tutarlılık sorunlarına yol açabilir. Bu, özellikle para işlemleriyle çalışan programlar için bir sorun olabilir.
  • Bellek Kullanımı: Non-blocking senkronizasyon, bir iş parçacığının bir kaynağa erişmeden önce ne kadar süre bekleyeceğini tahmin etmeyi zorlaştırabilir. Bu, program akışını ve performansı planlamayı zorlaştırabilir.

Data Register

  • Data register, bir mikroişlemci içinde bulunan ve geçici olarak veri depolamak için kullanılan bir tür kayıttır.
  • Bir programda oluşturulan değişkenler ve değerlerin daha hızlı ve pratik erişimi için, mikro işlemci kendi belleğine ek, bir de kendi içerisinde sınırlı sayıda bulunan Data Register diye nitelendirilen kayıtçılara bu değişkenleri değerleri ile birlikte kaydeder ve burada tutar.

Data Register’in bu işlemini birnevi Cache’leme gibi düşünebiliriz.

  • Data Register’in bazı önemli özellikleri:
  • Hızlı erişim: Veri kayıtları, CPU’nun diğer bellek türlerine kıyasla çok daha hızlı erişilebilir. Bu, işlemcinin talimatları daha hızlı işlemesine ve gecikmeleri en aza indirmesine olanak tanır.
  • Küçük boyut: Veri kayıtları, yalnızca birkaç bayt veri tutabilen küçük bellek konumlarıdır. Bu, CPU’nun bellek kullanımını optimize etmesine ve güç tasarrufu yapmasına yardımcı olur.
  • Özel amaçlı: Veri kayıtlarının her biri, belirli bir tür veriyi depolamak için tasarlanmıştır. Örneğin, bazı veri kayıtları genel amaçlıdır ve herhangi bir tür veriyi tutabilirken, diğerleri yalnızca sayıları veya karakterleri depolamak için kullanılabilir.

AMA

  • Herşeyde olduğu gibi Data Register kullanırken de bazı olumsuzlukların gerçekleşme riski vardır.
  • Şimdi düşünelim ki herhangi bir T zamanında Mikroişlemciye gelen bir değişken ve değeri hem kendi belleğine hem de data register’a kaydedildi.
  • Bu değişken üzerinde T+ zamanında yapılan bir değişiklik direkt mikroişlemcinin kendi belleğine yansıyacaktır fakat bu değişiklik anında data register’a yansıtılamamaktadır.
  • İşte bu ihtimalden kaynaklı herhangi bir T++ zamanında değişkenin değeri data register’dan çağırıldığında eski değeri getirme söz konusu olabilir.
  • Programcı isterse bu riski göze alabilir. Fakat iradeli bir şekilde bu tarz bir olumsuzluğun gerçekleşmesinin önüne geçmemize yarayacak araçlar mevcuttur.
  • Bu riskin Senkron veya single thread çalışmalarda gerçekleşme olasılığı çok düşüktür. Burada bu tutarsızlık durumunun gerçekleşme ihtimali daha yüksek olan teknikler genellikle asenkron ve multi-thread şekilde yazılmış programlardır.

VOLATILE Keyword’u

  • Bir önceki maddede bahsettiğimiz Data Register’daki tutarsızlığın önüne geçme ve doğru veriye erişme konusunda volatile keywordü bizlere yardımcı olmaktadır.
  • Bir programda volatile ile işaretlenmiş bir değişkenin, data register optimizasyonuna uğramadan direkt olarak bellek üzerinden gelmesini sağlamış oluruz.
  • Yani volatile ile işaretlenmiş değişkenlerde data register kullanılmaksızın yapılan tüm işlemler bellek üzerinden seyredecektir.
  • Bu sayede hem asenkron hem de multi-thread çalışmalarda bellek tutarsızlığı kaygısı yaşanmayacak, gönül rahatlığıyla operasyonlar devam edecektir.
  • Yani özetle volatile keywordü, bir değişkenin değerine yapılan okuma ve yazma işlemlerinde derleyici tarafından yapılan optimizasyonları devre dışı bırakan ve tutarsızlıkların önüne geçmeye yarayan bir araçtır.

Aslında burada da bir non-blocking senkronizasyon tekniği uygulamış oluruz.

Volatile anahtar kelimesini kullanmanın bazı önemli avantajları şunlardır:

  • Veri tutarlılığını sağlar: Volatile anahtar kelimesi, birden fazla iş parçacığının aynı değişkene erişmesi durumunda bile, değişkenin değerinin her zaman en güncel değerini yansıttığından emin olmanızı sağlar. Bu, veri tutarsızlıkları ve hatalardan kaçınmanıza yardımcı olur.
  • Donanım erişimini kontrol eder: Volatile anahtar kelimesi, bir değişkenin doğrudan donanım tarafından erişilebilir olduğunu belirtmek için de kullanılabilir. Bu, örneğin, bir sensörden gelen verileri okumak veya bir aktüatörü kontrol etmek için yararlı olabilir.
  • Bellek optimizasyonunu engeller: Derleyiciler, genellikle programın performansını artırmak için bellek optimizasyonları yapar. Ancak, bu optimizasyonlar bazen veri tutarsızlıklarına yol açabilir. Volatile anahtar kelimesi, derleyicinin belirli bir değişken için optimizasyon yapmasını engellemek için kullanılabilir.

Ancak volatile anahtar kelimesini kullanmanın bazı dezavantajları da vardır:

  • Performans düşüşü: Volatile anahtar kelimesi, derleyicinin belirli bir değişken için optimizasyon yapmasını engellediği için programın performansını biraz düşürebilir.
  • Kod karmaşıklığı: Volatile anahtar kelimesi, kodunuzu daha karmaşık hale getirebilir ve okunmasını zorlaştırabilir.
  • Gereksiz kullanım: Volatile anahtar kelimesini yalnızca gerçekten gerekli olduğunda kullanmak önemlidir. Aksi takdirde, programınızın performansını ve karmaşıklığını artırabilirsiniz.
  • Çözemediği sorunlar vardır: Deadlock ve race condition gibi durumlara karşın çözğm için yeterli değildir. Bu tür durumlar için blocking başlıgında incelediğimiz gibi daha sofistike senkronizasyon mekanizmaları kullanmalıyız.

Hangi senaryolarda kullanılmalı ?

  • Tek Okuma ve Çok yazma senaryolarında: Bir değişkenin sadece tek bir thread tarafından yazıldığı ve başka bir thread tarafından okunduğu senaryolarda daha uygun olacaktır.
  • Flog değişkenlerin kullanımında: bir thread’in çalışma durumunu bir başka thread’e bildirmek için kullanılan flog değişkenlerinde de volatile keywordü kullanılması yerinde olacak çünkü o değişkenin değerini bizim direkt bellekten almamız gerekir.
  • Performansın kritik olmadığı durumlarda: mikroişlemcinin değişken kullanım sürecindeki getirisi olan performans optimizasyon davranışının önemsenmediği durumlarda da kullanılabilir.
  • İleri düzey senkronizasyon Gerektirmeyen Durumlarda: Karmaşık olmayan basit senkronizasyon davranışı gerektiren durumlarda çözüm amaçlı volatile keywordü kullanılabilmektedir.

Hangi senaryolarda kullanılmamalı ?

  • Yüksek seviyeli senkronizasyon gerektiren durumlarda: yani kompleks derecede thread çalışmasının yapıldığı senaryolarda senkronizasyon amaçlı volatile keywordü kapsam açısından yeterli olmayacağından dolayı tercih edilmemeli.
  • Thread koordinasyonu gerektiren durumlarda: threadler arası işbirliği ve koordinasyon gerektiren senaryolarda volatile keywordü yetersiz kalabilir. Bu tarz durumlarda senkronizasyon mekanizmalarıyla çözüm aranmalıdır.
 internal class Program
{
private static void Main(string[] args)
{
Run();
}
volatile static int i; //volatile variable
private static void Run()
{
Thread thread1 = new(() =>
{
while (true)
i++;
});
Thread thread2 = new(() =>
{
while (true)
{
Console.WriteLine(i);
}
});
Thread thread3 = new(() =>
{
while (true)
i--;
});
thread1.Start();
thread2.Start();
thread3.Start();


}
  • Yukarıdaki örnekte de görüldüğü gibi i değişkeni volatile keywordü ile tanımlanmış, yani değerinin daima bellekten alınması istendiği belirtilmiştir. Bu sayede olası data register tutarsızlığının önüne geçilmiştir.

Eğer Volatile keywordü ile işaretlenmemiş bir değişkende volatile davranışı sergilemek istersek, bu durumda C#’daki System.Threading namespace’i altındaki Volatile sınıfının Volatile.Write ve Volatile.Read metotlarından faydalanabiliriz.

Bu sayede ilgili değişkende sadece kritik noktalarda volatile davranışı sergileyerek hem verideki tutarlılığı, hem de davranışın kullanılmadığı yerde data register optimizasyonu kolaylığından faydalanmış oluruz.

 internal class Program
{
private static void Main(string[] args)
{
Run();
}
static int i;
private static void Run()
{
Thread thread1 = new(() =>
{
while (true)
Volatile.Write(ref i, Volatile.Read(ref i) + 1); // örnek volatile davranışı
});
Thread thread2 = new(() =>
{
while (true)
{
Console.WriteLine(Volatile.Read(ref i));
}
});
Thread thread3 = new(() =>
{
while (true)
Volatile.Write(ref i, Volatile.Read(ref i) - 1);
});
thread1.Start();
thread2.Start();
thread3.Start();
}
}
  • Yukarıda volatile keywordü ile değil, Volatile sınıfı metotları kullanılarak gerçekleştirilen senkronizasyon yaklaşımını görüyorsunuz.

SpinLock

  • Spinlock, C# dilinde çoklu iş parçacıklı programlamada kullanılan senkronizasyon tekniklerinden bir tanesidir.
  • Aslında önceki yazılarda bahsettiğimiz spinning yapısıyla aynı mantıktadır.
  • Spinlock, blocking gibi diğer senkronizasyon tekniklerine göre daha hızlıdır çünkü bir kilit elde etmek için beklemek-uyutulmak yerine, kilit serbest olana kadar meşgul döngüde (spin) kalır.
  • Blocking kavramında davranışsal olarak işletim sistemi tarafından uygulanma durumu vardır. Fakat spinningde işletim sistemiyle herhangi bir bağa sahip olmayan bir davranış söz konusudur.
  • Haliyle bu açıdan değerlendirdiğimizde performans açısından daha avantajlıdır.
  • Lakin olayın bir de CPU boyutu vardır.
  • Spinning’de bir thread uyutma veya bekletme durumu olmadığından işletim sisteminde herhangi bir yük yoktur. Fakat kaynak kullanılabilir hale gelmeyi beklerken CPU’yu aktif bir şekilde kullanacak, yani boşa bir tüketim söz konusu olacaktır.
  • Bu yüzden spinning yaklaşımı kısa süreli bekletme durumlarında daha etkilidir.

Yani özetle, SpinLock’un avantajı, kısa süreli kilitlemelerde etkilidir. Çünkü thread’ler uyku moduna geçmek yerine spinning davranışı sergileyerek bekletilecektir. Lakin uzun süreli kitlemeleri etkili değildir çünkü sürekli olarak spinning davranışı CPU kaynaklarını tüketecek ve bu da verimlilik kaybına yol açacaktır.

int value = 0;
SpinLock spinLock = new();
Thread thread1 = new(() =>
{
bool lockTaken = false;
try
{
spinLock.Enter(ref lockTaken);
if (lockTaken)
for (int i = 0; i < 999; i++)
Console.WriteLine($"Thread1 : {++value}");
}
finally
{
spinLock.Exit();
}
});
Thread thread2 = new(() =>
{
bool lockTaken = false;
try
{
spinLock.Enter(ref lockTaken);
if (lockTaken)
for (int i = 0; i < 999; i++)
Console.WriteLine($"Thread2 : {++value}");
}
finally
{
spinLock.Exit();
}
});

thread1.Start();
thread2.Start();

Diğer senkronizasyon tekniklerinde de olduğu gibi, spinningde de thread referans nesnesinin bekletilme anında oluşabilecek hatalar yüzünden programda gerçekleşebilecek deadlock durumlarını önlemek için daima try-catch-finally bloklarıyla kontrolü sağlamalı ve finally bloğunda ilgili referans nesnenin serbest bırakılacağını garanti etmeliyiz.

SpinWait

  • Spinwait özünde diğer araçlarda olduğu gibi bir senkronizasyon aracıdır.
  • Yapısal olarak çok kısa süreli bekleme durumlarında kullanmak üzere tasarlanmıştır ve özellikle düşük maliyetli senkronizasyon ihtiyaçlarını karşılamak için idealdir.
  • SpinLock yapısına nazaran daha hafif bir hacme sahip bu yüzden çok kısa sürede bekleme durumları için uygun bir seçenektir. Yani SpinLock’un biraz daha optimize edilmiş halidir.
bool waitMod = false, condition = false;            //without spinwait
Thread thread1 = new(() =>
{
while (true)
{
if (waitMod)
{
continue;
}

if (!condition)
{
continue;
}

Console.WriteLine("Thread1 working...");
}
});

Thread thread2 = new(() => //with spinwait
{
while (true)
{
SpinWait.SpinUntil(() =>
{
return waitMod || condition;
});

Console.WriteLine("Thread2 working...");
}
});

thread1.Start();
thread2.Start();
  • Yukarıdaki örnekte görüldüğü gibi, ilgili conditionlar gerçekleşene kadar thread SpinWait bloğu içerisinde spin edilerek bekletilecektir.

InterLocked Sınıfı

Interlocked Sınıfı Nedir?

Interlocked Sınıfı:

  • C# dilinde multi-threading yaklaşımı sergilenirken paylaşılan değişkenlere güvenli bir şekilde erişimi sağlamak için kullanılan bir sınıftır.
  • Atomic/bölünemez işlemler gerçekleştirerek çalışır. Bir thread bir değişkeni okurken veya yazarken, diğer thread’lerin müdahalesine izin vermez.
  • Böylece, multi-threading ve asenkron yaklaşımlarından kaynaklanan veri bütünlüğü sağlanır.

Atomic İşlemler:

  • Bir işlemin başından sonuna kadar bölünemez olduğu anlamına gelir.
  • Başka bir thread’in, okuma veya yazma işlemi gerçekleştirilirken müdahale edememesi durumudur. Bu, race condition gibi problemlerin önüne geçer.

Volatile Keyword’ü ile Farkı:

  • Volatile sadece basit okuma ve yazma operasyonları için kullanılan bir keyword’dür. Interlocked sınıfı ise daha gelişmiş erişim yönetimi sağlar.
  • Interlocked sınıfı, belirli işlemleri atomic olarak yapar. Volatile ise, sadece değişkenin bellekteki değerini güncel tutar.

Kullanılan Metotlar:

  1. Increment:
  • Bir değişkenin değerini atomic olarak +1 arttırır.
int i = 0;
Interlocked.Increment(ref i);

2. Decrement:

  • Bir değişkenin değerini atomic olarak -1 azaltır.
int i = 0;
Interlocked.Decrement(ref i);

3. Add:

  • Bir değişkene belirtilen değeri atomic olarak ekler.
int i = 0;
Interlocked.Add(ref i, 15);
Interlocked.Add(ref i, 5);

4. Exchange:

  • Bir değişkenin değerini belirtilen değerle değiştirir ve eski değeri döndürür.

int oldValue = Interlocked.Exchange(ref i, 15);

5. CompareExchange:

  • Bir değişkenin değerini belirli bir koşul sağlandığında belirtilen değerle değiştirir. Bu örnekte koşul üçüncü parametrenin 0 olmasıdır. ilgili i değişkeni 0 olduğunda exchange işlemi gerçekleşecektir.

int i = 0;
Interlocked.CompareExchange(ref i, 25, 0);

Özellikle Kullanım Alanları:

  • Basit tiplerdeki değişkenler üzerinde çalışıyorsanız ve senkronizasyon amacıyla kullanmanız gerekiyorsa, Interlocked sınıfı idealdir.
  • Özellikle kilit ile çözüm getiremeye çalıştığınızda hız ve performans açısından büyük fayda sağlar.

MemoryBarrier Metodu

MemoryBarrier Metodu:

  • Multi-threading yaklaşımlarında bellek erişimini düzenlemek ve optimize edilmiş sıralamayı sağlamak için kullanılır.
  • Bellekteki işlemlerin sıralanmasını kontrol eder ve beklenmeyen sıralama durumlarını engeller.

Bellek Düzenleme:

  • Bellek düzeninden kasıt, bir thread’in bellek değişiklikleri diğer thread’lere göre ne zaman görünür hale geleceğidir.
  • Compiler ve CPU optimizasyonları sonucunda beklenen sıralama ile ilgili problemleri çözmek için kullanılır.
  • Thread’lerin belleğe erişim sıralaması değişkenlik gösterebilir, bu da bellek tutarsızlıklarına neden olabilir.

MemoryBarrier Metodunun Kullanımı:

  • Kodun hemen öncesinde ve sonrasında çağrılarak thread’ler arasında senkronizasyon sağlanabilir.
  • Aksi takdirde, CPU optimizasyonları nedeniyle değişkenin değeri yanlış okunabilir ve beklenmedik değişimlere neden olabilir.

Full Fence ve Half Fence:

  1. Full Fence:
  • Thread.MemoryBarrier() komutu ile gerçekleştirilir.
  • Bu komut, önceki tüm bellek erişimlerini tamamlanmış hale getirir ve sonraki tüm bellek erişimlerini engeller.
  • Yani, thread için bir çit oluşturur.
Thread.MemoryBarrier();

2. Half Fence:

  • Interlocked sınıfındaki metotlar kullanılarak oluşturulabilir.
  • Bu, yalnızca belirli bir değişken üzerindeki bellek erişimlerini düzenler.
Interlocked.Increment(ref i);
Thread.MemoryBarrier();

Yazma İşlemi:


int i = 0;
Thread writeThread = new Thread(() => {
while (true) {
Interlocked.Increment(ref i);
Thread.MemoryBarrier();
// i değişkeninin güncel değeri diğer thread'ler tarafından görülebilir.
}
});

Okuma İşlemi:

Thread readThread = new Thread(() => {
while (true) {
Thread.MemoryBarrier();
// i değişkeninin güncel değerini alabilmek için kullanılır.
Console.WriteLine($"i: {i}");
}
});

writeThread.Start();
readThread.Start();
  • Monitor.Enter/Exit metotlarının kullanıldığı noktalarda da ‘Full Fence’ davranışı sergilenir.
  • ThreadPool kullanan asenkron callback yapıları, delegate’ler, thread’ler arası sinyalleme gibi işlemler de ‘Fence’ davranışları ile ilgilidir.

ReaderWriterLock & ReaderWriterLockSlim

ReaderWriterLock & ReaderWriterLockSlim:

  • Bu araçlar, birden çok thread’in bir kaynağa eşzamanlı olarak erişebileceği ancak sadece tek bir thread’in bu kaynağı değiştirebileceği senaryolarda kullanılır.
  • ReaderWriterLockSlim, ReaderWriterLock sınıfından daha hafif ve daha hızlıdır. Ayrıca dispose edilebilir bir özelliğe sahiptir.

Diğer Senkronizasyon Araçlarından Farkları:

  • Diğer senkronizasyon araçlarına nazaran farklı bir düzeyde kilitleme sağlarlar.
  • Diğer kilitleme mekanizmaları sadece bir kaynağa eşzamanlı erişimi kontrol ederken, bu araçlar okuma işlemlerinin birbiriyle çakışmadan eşzamanlı olarak gerçekleştirilebilmesini sağlar.
  • Yazma işlemlerini sadece tek bir thread’in gerçekleştirmesini sağlarlar.

Hangi Senaryolarda Bu Mekanizmaları Kullanmalıyız?

  • Bir kaynak sıklıkla okunuyor ve yazma işlemi nadiren gerçekleşiyorsa, bu tür yüksek okuma trafiği senaryolarında kullanmak için idealdir.
  • Yüksek yazma trafiği olan senaryolarda kullanılmamalıdır çünkü yazma işlemleri sırasında performans kaybına neden olabilirler

using System.Threading;

internal class Program
{
private static void Main(string[] args)
{
// 5 adet reader thread oluşturuluyor...
for (int i = 0; i < 5; i++)
new Thread(Read).Start();

// 2 adet writer thread oluşturuluyor...
for (int i = 0; i < 2; i++)
new Thread(Write).Start();
}

static ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim();
static int counter = 0;

static void Read()
{
for (int i = 0; i < 10; i++)
{
try
{
readerWriterLockSlim.EnterReadLock();
Console.WriteLine($"R : Thread {Thread.CurrentThread.ManagedThreadId} is reading : {counter}");
}
finally
{
readerWriterLockSlim.ExitReadLock();
}
Thread.Sleep(1000);
}
}

static void Write()
{
for (int i = 0; i < 10; i++)
{
try
{
readerWriterLockSlim.EnterWriteLock();
counter++;
Console.WriteLine($"W : Thread {Thread.CurrentThread.ManagedThreadId} is writing : {counter}");
Thread.Sleep(200);
}
finally
{
readerWriterLockSlim.ExitWriteLock();
}
}
}
}

Kaynak: Gençay Yıldız — Asenkron & Multithread Programlama

--

--