Sobes.tech
Middle

Co należy pamiętać podczas pracy z mutexami w Go?

sobes.tech AI

Odpowiedź od AI

Pracując z mutexami (sync.Mutex) w Go, ważne jest, aby pamiętać o następujących kwestiach:

  • Zabezpieczenie i odblokowanie: Mutex musi być koniecznie zablokowany (Lock()) przed dostępem do współdzielonych danych i odblokowany (Unlock()) po zakończeniu pracy z nimi. Użycie defer mu.Unlock() zaraz po mu.Lock() jest dobrą praktyką, aby uniknąć zapomnienia o odblokowaniu.
  • Ekskluzywny dostęp: Mutex zapewnia wyłączny dostęp do zasobu jednemu goroutine naraz.
  • Stan "zablokowany/odblokowany": Wywołanie Lock() na już zablokowanym mutexie spowoduje zablokowanie wywołującego goroutine do czasu, aż inny go odblokuje. Wywołanie Unlock() na odblokowanym mutexie wywoła panic w czasie działania.
  • Nie kopiować mutexów: Struktura sync.Mutex nie jest przeznaczona do kopiowania po pierwszym użyciu. Przekazywanie mutexa przez wartość lub kopiowanie struktury zawierającej mutex może prowadzić do nieprawidłowej synchronizacji i paniców.
  • Deadlock (zakleszczenie): Występuje, gdy dwie lub więcej goroutine oczekują na siebie, aby zwolniły zasoby. Typowy scenariusz: goroutine A blokuje R1 i czeka na R2, a goroutine B blokuje R2 i czeka na R1.
  • Livelock (żywe zakleszczenie): Sytuacja, gdy goroutine ciągle zmieniają stan w odpowiedzi na działania innych, nie wykonując przy tym użytecznej pracy i nie posuwając się do przodu. Jest to rzadsze niż deadlock.
  • Głodzenie (starvation): Sytuacja, gdy jedna lub więcej goroutine nie może uzyskać dostępu do współdzielonego zasobu z powodu ciągłego blokowania go przez inne. W mutexach Go głodzenie nie jest gwarantowane.
  • Użycie sync.RWMutex do odczytu/zapisu: Jeśli większość operacji to odczyty, a zapisy są rzadkie, sync.RWMutex może być bardziej wydajny. Pozwala na jednoczesne odczyty wielu goroutine, ale blokuje podczas zapisu.
  • Krótkie blokady: Staraj się trzymać blokady tak krótko, jak to możliwe. Im dłużej mutex jest zablokowany, tym więcej goroutine będzie czekać, co zmniejsza paralelizm.
  • Testy warunków wyścigu: Użycie flagi -race podczas kompilacji i uruchamiania testów (go test -race) pomaga wykryć warunki wyścigu, często spowodowane niewłaściwym użyciem mutexów.

Przykład użycia defer:

import (
	"sync"
)

type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc bezpiecznie zwiększa licznik dla podanego klucza.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Odblokowujemy po wyjściu z funkcji.
	defer c.mu.Unlock()
	c.v[key]++
}

// Value zwraca aktualną wartość licznika dla podanego klucza.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Odblokowujemy po pobraniu wartości.
	defer c.mu.Unlock()
	return c.v[key]
}