Sobes.tech
Middle

Czym jest mutex, jakie rodzaje mutexów istnieją i jak ich używać?

sobes.tech AI

Odpowiedź od AI

Mutex (od ang. MUTual EXclusion) to prymityw synchronizacji używany do zapewnienia wyłącznego dostępu do współdzielonego zasobu między konkurującymi gorutynami. Gwarantuje, że w każdym momencie tylko jedna gorutyna może posiadać mutex i odwoływać się do chronionych danych.

W Go pakiet sync zapewnia dwa główne typy mutexów:

  • sync.Mutex: Standardowy mutex. Udostępnia metody Lock() i Unlock(). Jeśli mutex jest już zablokowany, wywołanie Lock() blokuje bieżącą gorutynę do czasu odblokowania. Unlock() odblokowuje mutex.

    var mu sync.Mutex
    var counter int
    
    func increment() {
        mu.Lock() // Blokuje dostęp do counter
        counter++
        mu.Unlock() // Odblokowuje dostęp
    }
    
  • sync.RWMutex: Mutex do odczytu/zapisu. Pozwala wielu gorutynom jednocześnie odczytywać chroniony zasób, ale tylko jednej pisać. Udostępnia metody Lock(), Unlock() (do zapisu), RLock() i RUnlock() (do odczytu).

    var rwMu sync.RWMutex
    var data []int
    
    func readData() {
        rwMu.RLock() // Blokuje do odczytu
        // Odczyt danych
        fmt.Println(data)
        rwMu.RUnlock() // Odblokowuje odczyt
    }
    
    func writeData(val int) {
        rwMu.Lock() // Blokuje do zapisu
        // Zapis do danych
        data = append(data, val)
        rwMu.Unlock() // Odblokowuje zapis
    }
    

Użycie mutexów jest konieczne do zapobiegania warunkom wyścigu (race conditions), gdy wiele gorutyn próbuje jednocześnie modyfikować te same dane, co prowadzi do nieprzewidywalnych rezultatów.

Zasady użycia:

  1. Wywołać Lock() przed dostępem do chronionego zasobu.
  2. Wywołać Unlock() po zakończeniu dostępu. Zaleca się użycie defer mu.Unlock() dla gwarancji odblokowania nawet w przypadku paniki.
  3. Nie próbować blokować już zablokowanego mutexu z tej samej gorutyny (może prowadzić do deadlocka).
  4. Nie próbować odblokować niezablokowanego mutexu.
  5. Przy użyciu RWMutex, używać RLock/RUnlock do operacji odczytu i Lock/Unlock do operacji zapisu.
  6. Mutexy nie powinny być kopiowane po pierwszym użyciu. Przekazuj je przez referencję lub wskaźnik.

Przykład użycia defer:

func safeIncrement() {
    mu.Lock()
    defer mu.Unlock() // Gwarantuje odblokowanie
    counter++
}