CodeGym /Kurslar /C# SELF /ReaderWriterLockSlim ilə optimizasiyalar

ReaderWriterLockSlim ilə optimizasiyalar

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Niyə adi lock yetərli olmaya bilər?

Gəlin düşünək: bizim ümumi resursumuz var — məsələn, müştərilərin siyahısı, onu çoxaxınlı tətbiqimizdə bir yerdə saxlayırıq. Çox vaxt biz sadəcə bu siyahını oxuyuruq — məsələn, interfeysdə istifadəçiyə göstəririk, hesabatlar qururuq, məlumatı filtr və sort edirik. Yalnız nadir hallarda kimsə yeni müştəri əlavə edir və ya köhnəni silir.

Əgər biz adi lock istifadə etsək, oxumaq istəyən hər bir axın digərinin oxuması və ya daha pis halda yazması bitənə qədər gözləməli olacaq. Və əgər əməliyyatların təxminən 99%-i oxumağa düşürsə? Belə çıxır ki, biz bütün oxuyucuları lazımsız yerə yavaşladırıq, halbuki onlar paralel işləyə bilərdilər.

Elə burada xüsusi bir tip bloklama — Reader-Writer Lock köməyə gəlir. Onun əsas ideyası budur: bir çox axın eyni anda oxuya bilər, amma kimsə dəyişiklik etmək istəyirsə, o eksklüziv giriş götürür və oxuyucuları bloklayır.

2. Nəzəriyyə: ReaderWriterLockSlim nədir

Siniflər haqqında qısa

  • ReaderWriterLock — daha köhnə, yavaş, deadlocklara gətirə bilər. Yalnız .NET Framework 2.0 dəstəyi lazımdırsa istifadə edin.
  • ReaderWriterLockSlim — sürətli müasir alternativ (Slim: “yüngül”, “nazik”). Bu sinif tez-tez rəqabət aparan oxuma/yazma əməliyyatları üçün optimallaşdırılıb və çoxlu axınları olan, oxumanın yazmadan daha tez-tez baş verdiyi tətbiqlərdə istifadə üçün tövsiyə olunur.

İş prinsipi

ReaderWriterLockSlim üç bloklama rejimini həyata keçirir:

  • Read Lock (oxumaq üçün blok): bir çox axın eyni zamanda oxuya bilər.
  • Write Lock (yazmaq üçün blok): yalnız bir axın məlumatları dəyişə bilər və heç kim oxuya bilməz.
  • Upgradeable Read Lock (yüksəldilə bilən oxuma bloku): yalnız bir axın eyni zamanda bu cür bloğu saxlaya bilər; o oxuyur, amma lazım olsa write lock-a “yüksələ” bilər.

Bloklama sxemi

Oxuyan axın Yazan axın Upgradeable Read Lock olan axın
Oxuyan ✅/⛔
Yazan
Upgradeable Read ✅*

✅ — icazə verilir

⛔ — bloklanır

— yalnız əgər upgradeable read lock write-ə “yüksəlməyibsə”

Həmin vaxt istifadə etmək və etməmək lazımdır

Tövsiyə olunur:

  • Çox eyni vaxtda axınlar var və onlar əsasən ümumi məlumatları oxuyurlar (loqikat, cache, konfiqurasiyalar).
  • Yazı nadir hallarda baş verir (məsələn, saniyədə/dəqiqədə/saatda bir dəfə).
  • Oxuma performansını maksimum saxlamaq vacibdirsə və axınlara mane olmamaq lazımdır.

Məsləhət deyil:

  • Yazma/oxuma əməliyyatları təxminən bərabərdirsə (adi lock daha yaxşı olar).
  • Məlumatlar tez-tez dəyişirsə (gəlir azdır).

3. ReaderWriterLockSlim-in praktiki istifadəsi

Gəlin tədris tətbiqimizi genişləndirək! Əvvəl sadə müştəri kataloqu qurmuşduq və kolleksiyaya çoxaxınlı giriş yoxlamışdıq. İndi çətinliyi artırırıq: təsəvvür edin ki, bizim müştərilər siyahımız var və ona bir neçə axın müraciət edir (məsələn, bir servis yeni aksiya barədə hamıya məlumat verir, digər axın isə yeni müştərilər əlavə edir).

Əvvəlcə müştəri kolleksiyamızı təsvir edək:


// Müştəri sinfi — keçmiş mühazirələrdən tanış qəhrəman
public class Client
{
    public int Id { get; set; }
    public string Name { get; set; }
}

İndi ReaderWriterLockSlim vasitəsilə axın təhlükəsiz giriş üçün bir qabıq yaradaq:

using System;
using System.Collections.Generic;
using System.Threading;

public class ClientDirectory
{
    private readonly List<Client> _clients = new List<Client>();
    // Oxuma/yazma üçün Slim-lock
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    // Müştəri əlavə etmək (write lock)
    public void AddClient(Client client)
    {
        _lock.EnterWriteLock(); // Yalnız bir axın müştəri əlavə edə bilər
        try
        {
            _clients.Add(client);
            Console.WriteLine($"[Axın {Thread.CurrentThread.ManagedThreadId}] Müştəri əlavə olundu {client.Name}");
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    // Müştərilər siyahısının surətini almaq (read lock)
    public List<Client> GetClients()
    {
        _lock.EnterReadLock(); // Bir çox axın eyni zamanda oxuya bilər
        try
        {
            // Orijinalı kimsə səhvən dəyişməsin deyə surət qaytarırıq
            return new List<Client>(_clients);
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }
}

4. Çoxaxınlı oxuma və periodik yazı

Tutaq ki, 5 axın periodik olaraq bütün müştəriləri oxuyur (“hesabatlar qurur” və ya sadəcə maraqlanır kimlər var), və ayrıca bir axın bəzən yeni müştəri əlavə edir.

using System.Threading;

class Program
{
    static void Main()
    {
        var directory = new ClientDirectory();

        // Müştəri əlavə edən axın
        var writerThread = new Thread(() =>
        {
            for (int i = 1; i <= 5; i++)
            {
                directory.AddClient(new Client { Id = i, Name = $"Müştəri {i}" });
                Thread.Sleep(700); // Uzun əməliyyatın imitasiya edilməsi
            }
        });

        // Bir neçə oxuyan
        for (int j = 0; j < 5; j++)
        {
            int readerId = j + 1;
            new Thread(() =>
            {
                for (int k = 0; k < 10; k++)
                {
                    var clients = directory.GetClients();
                    Console.WriteLine($"[Oxuyan {readerId}][Axın {Thread.CurrentThread.ManagedThreadId}] Müştəri sayı: {clients.Count}");
                    Thread.Sleep(200); // Yazmadan tez-tez oxuyuruq
                }
            }).Start();
        }

        writerThread.Start();
        writerThread.Join();

        // Oxuyanlara sonlandırmaq üçün vaxt verək:
        Thread.Sleep(3000);
    }
}

Demək olar ki, həmişə bir neçə axın eyni vaxtda müştərilər siyahısını oxuya bilər (bir-birini gözləmədən), amma axın müştəri əlavə etdikdə — digər axınlar yazı bitənə qədər gözləməlidir. Bu oxuyucuları lazımsız bloklamadan çoxlu istəkləri idarə etməyə imkan verir.

5. Yüksəldilə bilən blok: UpgradeableReadLock

Bəzən belə olur: axın məlumatı oxuyur və yalnız bəzən, əgər şərt ödənirsə, onu dəyişmək istəyir. Görünür ki, read lock götürmək, şərti yoxlamaq, sonra read lock-dan çıxıb write lock almaq kifayətdir. Amma “aralıqda” başqa bir axın artıq məlumatı dəyişə bilər! Burada yüksəldilə bilən blok köməyə gəlir.

Onu istifadə etmək asandır:

public bool AddClientIfNotExists(string name)
{
    _lock.EnterUpgradeableReadLock(); // Yalnız bir axın belə blok saxlayır
    try
    {
        bool exists = _clients.Exists(c => c.Name == name);
        if (!exists)
        {
            _lock.EnterWriteLock(); // Write lock-a yüksəldirik
            try
            {
                _clients.Add(new Client { Name = name });
                return true;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
        return false;
    }
    finally
    {
        _lock.ExitUpgradeableReadLock();
    }
}

İlk öncə axın oxuyur, əgər dəyişiklik lazımdırsa — o səviyyəni yüksəldir, siyahını digər axınlara buraxmadan.

6. Faydalı incəliklər

Müqayisə: ReaderWriterLockSlim vs lock

lock
ReaderWriterLockSlim
Eyni vaxtda oxuma Xeyr Bəli
Eyni vaxtda yazma Xeyr Xeyr
Yüksəldilə bilən oxumanın dəstəyi Xeyr Bəli (EnterUpgradeableReadLock)
Sürət Çox sürətli Oxumalar tez-tezdirsə daha sürətli
Yaddaş Minimum Bir az daha çox
Sadəlik Asandır Biraz daha mürəkkəbdir, incə məqamlar var

Real layihələrdə necə görünür

Böyük tətbiqlərdə, yüzlərlə axın tərəfindən oxunan, amma nadir hallarda dəyişdirilən böyük cədvəllər və ya cache-lər varsa, ReaderWriterLockSlim oxuma zamanı “tıxanmanı” aradan qaldırmaq üçün yoludur. Məsələn:

  • Microservices-lərin istifadə etdiyi konfiqurasiya cache-i.
  • Maliyyə hesablamaları üçün istinadların saxlanması.
  • Mesaj routinqi üçün daxili cache.

Qeyd edim — əksər hallarda, xüsusən çoxaxınlılığa yeni başlayırsınızsa, adi lock daha sadə və təhlükəsizdir. ReaderWriterLockSlim isə yalnız oxumağın həqiqətən tez-tez olduğu hallarda lazım olan həllərdir.

Vizualizasiya: giriş sxemi

flowchart TD
    A[Oxuyan axın 1] --Read--> D[ReaderWriterLockSlim]
    B[Oxuyan axın 2] --Read--> D
    C[Yazan axın] --Write--> D
    D --Read icazə verilir--> E[Ümumi kolleksiyanın oxunması]
    D --Write oxuma/yazmanı bloklayır--> F[Ümumi kolleksiyanın yazılması]

Yalnız oxumalar olduğu müddətcə — hamıya giriş açılır; yazı cəhdi olarsa — hamı yazının bitməsini gözləyir.

7. ReaderWriterLockSlim: incə məqamlar və tipik səhvlər

Lock Recursion (Dövrü tutma): Default olaraq ReaderWriterLockSlim eyni axının eyni tip lock-u təkrarən tutmasına icazə vermir. Yəni, əgər axın artıq read lock saxlayır və ikinci dəfə onu almağa çalışarsa — istisna atılacaq. Write lock üçün də oxşar. Lakin, UpgradeableReadLock içəridən write lock-a yüksələ bilər.

Sıraları qarışdırmayın: Əvvəl EnterUpgradeableReadLock() → onun içində EnterWriteLock() çağırın. Amma əksinə etməyin!

İstisnalar: Əgər lock-u sərbəst buraxmasanız (məsələn, ExitWriteLock() çağırmasanız), digər axınlar həmişə gözləyəcək. Buna görə həmişə try { ... } finally { ... } konstruksiyalarından istifadə edin.

Uzun müddət saxlamayın: Lock daxilində uzun IO əməliyyatları etməyin. Axın lock-u nə qədər uzun saxlayarsa, digərləri üçün gecikmə bir o qədər çox olar. Lock bloklarından mümkün qədər tez çıxmağa çalışın.

Qarışığı qiymətləndirin: Kiçik əməliyyatları qorumaq üçün ReaderWriterLockSlim istifadə etməyin, burada adi lock daha sadə və daha sürətli ola bilər.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
ReaderWriterLockSlim istifadə edərək oxuma əməliyyatının təşkili
ReaderWriterLockSlim istifadə edərək oxuma əməliyyatının təşkili
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Axınların sinxronizasiyası
Ümumi resurslar problemi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION