CodeGym /Kurslar /C# SELF /Sinxronizasiya üçün mutexlər:

Sinxronizasiya üçün mutexlər: Mutex

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

1. Giriş

Əvvəlki mühazirədən bir nümunəni xatırlayaq: proqramımızda hələ sadə olan iki thread ümumi sayğacı inkrement edir, amma yekun dəyər həmişə gözlənilənlə üst-üstə düşmür. Bu sayğacı qorumaq üçün biz artıq keyword lock (dəqiq desək, Monitor) istifadə etmişdik, hansı ki yalnız bir proses daxilində sinxronizasiya üçün uyğundur. Amma əgər proqramınız tək istifadəçi deyilsə və resursu başqa proqramlar da istifadə etmək istəyirsə nə etməlisiniz? Məsələn, siz iki nüsxədə işə düşən superservis yazırsınız və hər ikisi eyni fayla yazmaq istəyir… və ya printer portu kimi avadanlığı idarə etmək istəyirlər? Burada köməyə qədim yaxşı mutex gəlir (mutual exclusion — "qarşılıqlı istisna").

Konsepsiya

Mutex — sinxronizasiya primitividir, o, təkcə bir proses daxilində olan threadlər arasında resursa çıxışı məhdudlaşdırmır, həm də eyni maşında olan fərqli proseslər arasında çıxışı koordinasiya etməyə imkan verir. Onu görüş otağı qapısında hər kəsin gördüyü böyük "Məşğul" lövhəsi kimi təsəvvür edin.

.NET-də bunun üçün nəzərdə tutulmuş sinif System.Threading.Mutex.

Nə vaxt Mutex həqiqətən lazımdır:

  • Fərqli proseslərdəki threadlər arasında çıxışı sinxronlaşdıranda (məsələn, iki ayrı tətbiq eyni fayl üzərində işləyir).
  • Resurs o qədər qiymətli və bölünməzdirsə ki, hətta proses kontekstinin özü də giriş hüquqlarını paylaşa bilməz.

Yalnız bir proses daxilində threadlər arasında sinxronizasiya üçün adətən lock (Monitor) istifadə olunur. Mutex interproses sinxronizasiya üçün üstünlük verilir, çünki o daha "ağır" və daha yavaşdır.

Mutex necə işləyir

flowchart TD
    A(Proses 1) --|Soruşur|--> M(Mutex)
    B(Proses 2) --|Soruşur|--> M
    M --|Yalnız birinə icazə|--> R(Ümumi resurs)
    A --|Azad edir|--> M
    B --|Azad olduqdan sonra|--> M

2. Mutex ilə işləməyin əsas sintaksisi

Yaratma

Mutex digər sinxronizasiya sinifləri kimi sadə şəkildə yaradılır:

using System.Threading;

Mutex mutex = new Mutex();

Əsas metodlar

  • WaitOne() — mutex-i ələ keçirməyə cəhd; alınmazsa — thread mutex azad olunana qədər bloklanır.
  • ReleaseMutex() — mutex-i azad edir və digər threadlərə və ya proseslərə kritik bölməyə daxil olmağa imkan verir.

Ən sadə nümunə: bir proses daxilində threadlər arasında sinxronizasiya

using System;
using System.Threading;

class Program
{
    static Mutex mutex = new Mutex();

    static void Main()
    {
        Thread t1 = new Thread(PrintNumbers);
        Thread t2 = new Thread(PrintNumbers);
        t1.Start();
        t2.Start();
        t1.Join();
        t2.Join();
    }

    static void PrintNumbers()
    {
        for (int i = 0; i < 5; i++)
        {
            mutex.WaitOne(); // Kritik bölməyə daxil olmaq
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: {i}");
            mutex.ReleaseMutex(); // Kritik bölmədən çıxmaq
            Thread.Sleep(100); // Görünənlik üçün
        }
    }
}

Bu nümunədə hər iki thread növbə ilə konsola yazı yazmaq üçün çıxış alır.

3. Adlandırılmış mutex ilə interproses sinxronizasiya

Proseslər arasında "ağır artilleriya" — sinxronizasiya üçün adlandırılmış (named) mutex istifadə olunur. Ona bir ad verirsiniz və kompüterdəki bütün proseslər ona müraciət edə bilirlər.

Mutex mutex = new Mutex(false, "MyApp_Mutex");

Konstruktor parametrleri:

  • Birinci parametr (bool initiallyOwned) — yaratdıqdan sonra thread avtomatik mutex-i ələ keçirmək istəyirmi. Adətən — false.
  • İkinci parametr — mutex-in adı. Eyni ada malik mutex-dən istifadə edən bütün proseslər eyni obyektə müraciət edirlər, məsələn "MyApp_Mutex".

Nümunə: iki tətbiq eyni Mutex-dən istifadə edir

Effekti görmək üçün eyni proqramı iki fərqli pəncərədən işə salın.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        using (Mutex mutex = new Mutex(false, "MySuperUniqueMutexName"))
        {
            Console.WriteLine("Kritik bölməyə daxil olmağa cəhd edilir...");
            mutex.WaitOne(); // Digər proses mutex-i azad edənə qədər gözləyir
            try
            {
                Console.WriteLine("Kritik bölmə bu proses tərəfindən tutulub.");
                Console.WriteLine("Kritik bölmədən çıxmaq üçün Enter basın.");
                Console.ReadLine();
            }
            finally
            {
                mutex.ReleaseMutex();
                Console.WriteLine("Kritik bölmə azad edildi.");
            }
        }
    }
}

Sınaq:

  1. Bu tətbiqdən iki pəncərə açın.
  2. Hər ikisini işə salın — ikinci biri ilkində Enter basana qədər gözləyəcək.

4. Proqramın eyni vaxtda bir neçə nüsxəsinin işə düşməsinin məhdudlaşdırılması

Mutex tez-tez eyni anda işə düşən nüsxələrin sayını məhdudlaşdırmaq üçün istifadə olunur. Məsələn: "Ey, istifadəçi, növbəti dəfə kalkulyatorun iki nüsxəsini açma!"

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        bool createdNew;
        using (Mutex mutex = new Mutex(true, "CalculatorAppInstanceMutex", out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("Tətbiq artıq işə düşüb!");
                return;
            }

            Console.WriteLine("Tətbiq uğurla başladı. Çıxmaq üçün Enter basın.");
            Console.ReadLine();
        }
    }
}

Bu pattern masaüstü tətbiqlərində tez-tez rast gəlinir: ilk nüsxə işə düşür, ikinci isə sadəcə xəbər verib çıxır. Microsoft-un rəsmi nümunəsi — dokumentasiyada.

5. Mutex ilə işləyərkən tipik səhvlər

Səhv №1: ReleaseMutex()-i çağırmağı unutmaq.
Əgər thread mutex-i uğurla ələ keçiribsə (WaitOne()), amma ReleaseMutex()-i çağırmayıbsa (istisna ilə çökanıb və ya sadəcə unutubsa), heç bir digər thread və ya proses daxil ola bilməyəcək, ta ki "unutqan" thread bitənə qədər. Bu deadlock-a səbəb ola bilər. Yaxşı praktika — həmişə try-finally istifadə etməkdir:

mutex.WaitOne();
try
{
    // kritik bölmə
}
finally
{
    mutex.ReleaseMutex();
}

Səhv №2: ReleaseMutex()-i yanlış sayda çağırmaq.
Əgər ReleaseMutex()-i daha çox dəfə çağırsanız, nə qədər WaitOne() çağırılıbsa ondan artıq olsa, ApplicationException atılacaq.

Səhv №3: öz olmayan Mutex-i azad etməyə cəhd etmək.
Mutex onu ələ keçirən thread-ə "bağlıdır". Yalnız həmin thread-in ReleaseMutex()-i çağırmaq hüququ var. Əgər başqa bir thread onu azad etməyə çalışsa, .NET istisna atacaq.

Səhv №4: Mutex-i istifadə etmək, halbuki lock kifayətdir.
Mutex adi lock-dan daha yavaşdır, çünki interproses iş üçün sistem çağırışları tələb edir. Tövsiyə: əgər interproses sinxronizasiya lazım deyilsə — lock istifadə edin.

Səhv №5: mutex üçün uyğun olmayan ad seçmək.
Çox sadə ad seçsəniz (məsələn, "MyMutex"), təsadüfən başqa bir proqramla adını paylaşa bilərsiniz. Daha unikal adlar istifadə etmək yaxşıdır (məsələn, şirkətinizin və ya tətbiqinizin adı ilə).

6. Faydalı incəliklər

WaitOne(timeout)

Mutex üçün gözləmə timeout-u təyin etmək olar:

if (mutex.WaitOne(5000)) // 5 saniyəyə qədər gözləyir
{
    try { /* ... */ }
    finally { mutex.ReleaseMutex(); }
}
else
{
    Console.WriteLine("5 saniyə ərzində resursa çıxış alınmadı!");
}

Mutex.TryOpenExisting

Artıq mövcud olan mutex-ə qoşulmaq lazımdırsa, statik metodu istifadə edin:

if (Mutex.TryOpenExisting("DiaryFileWriteMutex", out Mutex existingMutex))
{
    // indi existingMutex mövcud mutex-ə istinaddır
}

İstifadəçilər arasında bölüşdürmə

Adlandırılmış mutex defolt olaraq sistem obyektlərini yaratmaq hüququ olan bütün istifadəçilər üçün əlçatandır. Daha sərt nəzarət tələb olunursa — MutexSecurity ilə konstruktoru istifadə edin.

Müqayisə cədvəli: lock/Monitor, Mutex, Semaphore

Primitiv Interproses sinxronizasiya? Sürət Harada istifadə etmək
lock/Monitor
Xeyr Çox yüksək Bir proses daxilindəki threadlər arasında
Mutex
Bəli Daha aşağı (daha baha) Fərqli proseslərin threadləri arasında
Semaphore
Bəli (NamedSemaphore üçün) Mutex ilə müqayisə ediləndə oxşar Thread sayını məhdudlaşdırmaq lazım olanda

Semafoflar haqqında daha ətraflı — növbəti mühazirədə.

Mutex vəziyyətləri

stateDiagram-v2
    [*] --> Unowned
    Unowned --> Owned : WaitOne()
    Owned --> Owned : WaitOne() (reentrant)
    Owned --> Unowned : ReleaseMutex()
    Owned --> Abandoned : thread "öldü", ReleaseMutex() çağırılmadı
    Abandoned --> Unowned
  • Unowned — heç kim mutex-ə sahib deyil.
  • Owned — mutex bir thread tərəfindən ələ keçirilib.
  • Abandoned — thread qəflətən bitdi və ReleaseMutex()-i çağırmadı. Növbəti mutex-i alan kəs AbandonedMutexException istisnası alacaq (bu səhvin və resursun potensial qeyri-konsistent vəziyyətinin göstəricisidir).
2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Resursun sadə qorunması Mutex ilə
Resursun sadə qorunması Mutex ilə
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION