Lektionen des Moduls (4/5)
`sync.Mutex` und `sync.WaitGroup`
Kanäle sind die Hauptsprache von Go, manchmal jedoch auch traditioneller
Es werden Primitive benötigt. Das sync-Paket stellt Mutexe bereit, warten Sie
Gruppen und andere Dienstprogramme zur Koordinierung des gemeinsamen Status.
sync.Mutex: exklusive Sperre
import "sync"
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}Zwischen Lock() und Unlock() tritt jeweils nur eine Goroutine ein.
Obligatorisches Muster: defer mu.Unlock() direkt nach Lock().
sync.RWMutex: viele Leser, ein Autor
Für den leseintensiven Zustand:
var mu sync.RWMutex
mu.RLock() // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()
mu.Lock() // write lock: esclusivo
data = newValue
mu.Unlock()Nur nützlich, wenn die Lesevorgänge tatsächlich die Schreibvorgänge überwiegen; ansonsten ein Stammgast
sync.Mutex ist dank geringerer interner Geschwindigkeit einfacher und oft schneller
Streit.
sync.WaitGroup: Warte auf N Goroutinen
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // blocca finché tutti i Done() sono arrivatiArbeitsablauf:
wg.Add(N)erhöht den Zähler um N (bevor die Goroutinen gestartet werden).- Jede Goroutine ruft
wg.Done()auf, wenn sie fertig ist (idealerweise überdefer). wg.Wait()blockiert, bis der Zähler wieder auf 0 geht.
sync.Once: Thread-sichere verzögerte Initialisierung
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}Der Rückruf in once.Do wird genau einmal ausgeführt, auch bei gleichzeitiger Ausführung
Anrufe. Singleton/Lazy-Initialisierungsmuster.
Mutex vs. Kanal: Wann was verwenden
Richtlinien (auch aus der offiziellen Go-FAQ):
| Fall | Bevorzugtes Werkzeug |
|---|---|
| Übergabe des Eigentums an Daten | Kanal |
| Arbeit verteilen (Arbeitswarteschlange) | Kanal |
| Koordinierung unabhängiger Goroutinen | Kanal |
| Ein Strukturfeld schützen | Mutex |
| Cache / Zähler | Mutex oder atomar |
| Singleton-Referenz | sync.Once |
„Zu orchestrierende Kanäle, Mutexe für gemeinsam genutzte Daten“ ist eine gute Faustregel.
Rassendetektor
Führen Sie Tests mit -race durch, um Datenrennen zu erkennen:
go test -race ./...
go run -race main.goDer Race-Detektor instrumentiert die Binärdatei und protokolliert jede Unsynchronisation Zugriff auf den gemeinsamen Speicher. Es ist das Tool Nr. 1 zur Validierung von gleichzeitigem Code vor der Produktion.
Probieren Sie es aus
Schützen Sie die Erhöhung der Anzahl mit mu.Lock() + defer mu.Unlock().
Hinweis anzeigen
Das Aufschieben von mu.Unlock() direkt nach Lock() garantiert die Freigabe auch bei Panik.
Lösung nach 3 Versuchen verfügbar
Starten Sie 3 Goroutinen und warten Sie mit sync.WaitGroup (Add(3), Done() in jeder Goroutine, Wait()) auf alle.
Hinweis anzeigen
VOR dem Start der Goroutine hinzufügen; INNERHALB der Goroutine mit „Defer“ erledigt.
Lösung nach 3 Versuchen verfügbar
Welches Muster wird empfohlen, um sicherzustellen, dass der Mutex freigegeben wird?
mu.Lock()
// ?Zusammenfassung
sync.Mutexschützt den gemeinsamen Status; immerdefer mu.Unlock().- Verwenden Sie einen Zeigerempfänger, um das Kopieren des Mutex zu vermeiden.
sync.RWMutexfür Muster mit vielen Lesern/einem Schreiber (nur wenn es hilft).sync.WaitGroup:Addvorgo,Donemitdefer,Waitzum Warten.sync.Once: Thread-sichere verzögerte Initialisierung.- „Zu orchestrierende Kanäle, Mutexe für gemeinsam genutzte Daten“.
defer mu.Unlock()0: Tool Nr. 1 zum Erkennen von Datenrennen.