CodeGym /Kurslar /C# SELF /Semaforlar: Semaphore

Semaforlar: SemaphoreSemaphoreSlim

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

1. Giriş

Mutexlock — tək bir müştərini xidmət edən barista kimidir. Amma əgər bizim bir kofe maşınımız yox, üçü varsa — və eyni anda üç fincan qəhvə hazırlana bilərsə, nə etmək lazımdır?

Məsələn, üç kofe maşını olan kafen var. Müştərilər (thread-lər) gəlir, boş maşını tuturlar, qəhvəni hazırlayıb gedirlər. Əgər bütün üç maşın məşğuldursa, qalanlar boşalmağı gözləyir.

Sual: Necə etmək olar ki, eyni vaxtda maksimum üç müştəri maşınlarda işləsin, digərləri isə növbədə dayansın?
Cavab: semafordan istifadə et!

Semafor nədir?

Semafor — sinxronizasiya üçün klassik alətdir. Əgər lock/Mutex "bir daxil oldu — qalanlar gözləyir" deyirsə, semafor belə bildirir: "mən eyni vaxtda N icazə verirəm!".

Semaforlar 1965-ci ildə Edsger Dijkstra tərəfindən təklif olunub. Adı dəniz siqnalizasiyasından gəlir: bayraqlar necə məlumat verirdisə, semafor da thread-lərə deyir — daxil ola bilərsiniz ya gözləməlisiniz.

İstifadə ssenariləri

  • Resursla eyni anda işləyən threadlərin sayını məhdudlaşdırmaq.
  • Verilənlər bazasına eyni anda qoşulanların sayı, paralel sorğuların sayı, ağır tapşırıqlar üçün limit qoymaq.

2. Siniflərin icmalı: SemaphoreSemaphoreSlim

Semaphore

  • Ağır sinifdir, kernel obyektlərindən istifadə edir.
  • Fərqli proseslər arasındakı sinxronizasiyanı dəstəkləyir.
  • Ad təyin edib proseslər arasında paylaşmaq olar.

SemaphoreSlim

  • Yüngülləşdirilmiş versiyadır, yalnız bir proses daxilində işləyir.
  • Daha sürətlidir və resurslara qənaət edir.
  • Əgər proseslərarası sinxronizasiya lazım deyilsə, demək olar ki hər zaman üstünlük verilir.

Analogi: səyahət çantası (SemaphoreSlim) vs böyük çamadan (Semaphore). Yüngül səyahət edirsənsə, çantanı götür.

Müqayisəli cədvəl

Sinif Proseslərarası Performans Tövsiyə edilir
Semaphore
Bəli Daha yavaş Proseslərarası sinxronizasiya lazım olanda
SemaphoreSlim
Xeyr Daha sürətli 99% hallarda, bir proses daxilində

Semaforun əsas metodları və xassələri

Əsas parametrlər

  • InitialCount — başlanğıc icazə sayı.
  • MaxCount — eyni anda verilə biləcək maksimum icazə.

Açar metodlar

  • Wait() və ya WaitAsync() — giriş tələb etmək (icazəni tutmaq).
  • Release() — icazəni azad etmək.

Necə işləyir
Əgər Wait() çağırışında icazə yoxdursa, thread bloklanır və kimisə Release() çağırana qədər gözləyir. İstifadəyə görə azad ediləndən sonra gözləyənlərdən biri davam edir.

3. İlk praktik nümunə

Konsol tətbiqinə 3 yerlik "parkovka" əlavə edək və 10 thread-ə işə salaq.

using System;
using System.Threading;

class Program
{
    // 3 icazəli semafor (3 parkovka yeri)
    static SemaphoreSlim parking = new SemaphoreSlim(3);

    static void Main()
    {
        for (int i = 1; i <= 10; i++)
        {
            int carNumber = i;
            new Thread(() =>
            {
                Console.WriteLine($"Maşın #{carNumber} parkovkaya daxil olmağa çalışır...");
                parking.Wait(); // Boş yer gözləyir
                Console.WriteLine($"Maşın #{carNumber} parkovkaya daxil oldu!");
                Thread.Sleep(2000); // 2 saniyə parkda qalırıq
                Console.WriteLine($"Maşın #{carNumber} parkovkadan çıxır.");
                parking.Release(); // Yeri azad edirik
            }).Start();
        }
    }
}
  • Eyni anda yalnız üç maşın "parklanacaq".
  • Qalanlar yer azalana qədər gözləyəcəklər.
  • Çıxış qarışa bilər — bu çoxthreadli proqram üçün normaldır.

4. Semafor yük məhdudlaşdırıcısı kimi

Ağır tapşırıqların (məsələn, yükləmələrin) eyni anda icrasını 5-ə qədər məhdudlaşdıraq.

static SemaphoreSlim semaphore = new SemaphoreSlim(5); // maksimum 5 eyni vaxtda yükləmə

static void DownloadFile(int fileId)
{
    semaphore.Wait();
    try
    {
        Console.WriteLine($"--> Fayl {fileId} yüklənməyə başlayır");
        Thread.Sleep(1000 + fileId * 100); // Yükləmə (imitasiya)
        Console.WriteLine($"<-- Fayl {fileId} yükləndi");
    }
    finally
    {
        semaphore.Release();
    }
}

static void Main()
{
    for (int i = 1; i <= 12; i++)
    {
        int localId = i;
        new Thread(() => DownloadFile(localId)).Start();
    }
}

Vacib məqam: Wait()-i try blokundan əvvəl çağır və Release()-i finally-də qoy. Beləliklə icazə istisna baş versə belə azad olunacaq.

5. Wait(int millisecondsTimeout) və asinxron metodlar

Sadəcə müəyyən müddət gözləmək də olar:

if (semaphore.Wait(500))
{
    // Yarım saniyə ərzində icazə almaq mümkün oldu!
}
else
{
    // 500 ms içində gözləmədik — dayandıq
}

Müasir tətbiqlərdə (məsələn, ASP.NET) asinxron variantdan istifadə et: await semaphore.WaitAsync(). Bu, icazə gözlənilərkən thread-i bloklamır.
Qeyd: asinxron kodda məhz SemaphoreSlim və onun WaitAsync-indən istifadə et, yoxsa gözlənilməz deadlock-lar ala bilərsən.

6. Yanlış və düzgün nümunələr

Tez-tez edilən səhv — Release()-i unutmaq: icazələr "axır" və hər şey dayanır.

Pis

static void SomeWork()
{
    semaphore.Wait();
    // ... işləmə, amma Release unutuldu!
}

Yaxşı

static void SomeWork()
{
    semaphore.Wait();
    try
    {
        // işləmə
    }
    finally
    {
        semaphore.Release();
    }
}

Asinxron variant

static async Task SomeAsyncWork()
{
    await semaphore.WaitAsync();
    try
    {
        // asinxron işləmə
    }
    finally
    {
        semaphore.Release();
    }
}

7. Semaforun daxili işi (sadə dildə)

Semafor bir sayğacdır. Wait() onu 1-ə azaldır. Əgər sayğac > 0 idisə — thread keçir; əks halda 0 olarsa — thread gözləyir. Release() sayğacı artırır və gözləyənləri oyadır.


+-------------------------------+
| Semafor (sayğac = 3)         |
+-------------------------------+
|  [ ]  [ ]  [ ]                | <--- İcazələr
+----+----+----+----------------+
     |    |    |
   Thread Thread Thread

8. Faydalı nüanslar

Digər primitivlərlə fərqi

  • lock / Monitor / Mutex — yalnız bir thread-i buraxır (eksklüziv erişim).
  • Semaphore/SemaphoreSlim — eyni anda məhdud sayda, N thread-i buraxır.

Semafor "sahibli ilə bağlı" deyil: icazəni hər hansı bir thread azad edə bilər. Bu xüsusiyyətdir, səhv deyil.

Real həyatda tətbiqlər

  • Servisə və ya DB-ə paralel bağlantıların limitlənməsi.
  • Pul: resursda eyni anda ən çox N thread.
  • Eyni anda işlənən web sorğularının məhdudlaşdırılması.
  • Yüklənmədən qorunmaq üçün oxu/yazma limitləri.
  • Xarici API çağırışlarının limitlənməsi.

Səhv nümunəsi (Release daha çox çağırılıb)

var semaphore = new SemaphoreSlim(2);
semaphore.Release(); // Səhv! Sayğac 3 oldu, MaxCount-ı keçdi — SemaphoreFullException atılacaq.

Burada SemaphoreFullException yaranacaq: sayğac maksimumu keçdi.

SemaphoreSemaphoreSlim arasındakı fərqlər

  • SemaphoreSlim — proses daxilidir, daha sürətli və sadədir (demək olar ki, həmişə istifadə et).
  • Semaphore — proseslərarası sinxronizasiya üçün lazımdır (nadir ssenari).

Niyə semaforları bilmək lazımdır?

İntervyuda tez verilən sual: "Resursla işləyən threadlərin sayını necə məhdudlaşdırmaq olar?" — düzgün cavab: semafor.

  • lock — 1 thread.
  • Semaphore/SemaphoreSlimN thread.

9. Tipik səhvlər və istifadə xüsusiyyətləri

Səhv №1: Release()-i çağırmağı unutmaq. Əgər thread icazəni tutub (Wait() və ya WaitAsync()) amma onu azad etmirsə, digərləri sonsuz müddət gözləyəcək — tətbiq "donacaq".

Səhv №2: Release()-i Wait()-dən daha çox çağırmaq. Artıq "əlavə" icazələr yaranır. Semaphore üçün bu SemaphoreFullException-a gətirib çıxaracaq və giriş məntiqini poza bilər.

Səhv №3: müxtəlif sinxronizasiya mexanizmlərini qarışdırmaq. Bir yerdə lock, başqa yerdə semafor istifadə etmək eyni resursa qarşı. Bu deadlock riskini artırır.

Səhv №4: asinxron koda Semaphore-dən istifadə etmək. Klassik semafor async/await-lə yaxşı işləmir. Asinxron ssenarilər üçün SemaphoreSlimWaitAsync()-dən istifadə et.

Səhv №5: yanlış initialCountmaxCount seçmək. Düzgün olmayan dəyərlərlə məhdudiyyəti aşa və resursa gözləniləndən artıq thread daxil ola bilər.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION