Dies ist ein Auszug aus dem Beitrag; Der vollständige Beitrag ist hier verfügbar: https://victoriametrics.com/blog/go-sync-cond/
Dieser Beitrag ist Teil einer Serie über den Umgang mit Parallelität in Go:
In Go ist sync.Cond ein Synchronisierungsprimitiv, obwohl es nicht so häufig verwendet wird wie seine Geschwister wie sync.Mutex oder sync.WaitGroup. Sie werden es in den meisten Projekten oder sogar in den Standardbibliotheken selten sehen, wo normalerweise andere Synchronisierungsmechanismen an seine Stelle treten.
Dennoch möchten Sie als Go-Ingenieur nicht wirklich Code lesen, der sync.Cond verwendet, und keine Ahnung haben, was vor sich geht, weil es schließlich Teil der Standardbibliothek ist.
Diese Diskussion wird Ihnen helfen, diese Lücke zu schließen, und noch besser, sie wird Ihnen ein klareres Gefühl dafür vermitteln, wie es in der Praxis tatsächlich funktioniert.
Lassen Sie uns also erklären, worum es bei sync.Cond geht.
Wenn eine Goroutine darauf warten muss, dass etwas Bestimmtes passiert, wie etwa die Änderung gemeinsam genutzter Daten, kann sie „blockieren“, was bedeutet, dass sie ihre Arbeit einfach pausiert, bis sie grünes Licht zum Fortfahren erhält. Der einfachste Weg, dies zu tun, ist eine Schleife, vielleicht sogar das Hinzufügen eines time.Sleep, um zu verhindern, dass die CPU durch „Busy-Waiting“ verrückt wird.
So könnte das aussehen:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Das ist nicht wirklich effizient, da diese Schleife immer noch im Hintergrund läuft und CPU-Zyklen durchläuft, selbst wenn sich nichts geändert hat.
Hier kommt sync.Cond ins Spiel, eine bessere Möglichkeit, Goroutinen ihre Arbeit koordinieren zu lassen. Technisch gesehen handelt es sich um eine „Bedingungsvariable“, wenn Sie einen eher akademischen Hintergrund haben.
Hier ist die grundlegende Schnittstelle, die sync.Cond bietet:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
Okay, schauen wir uns ein kurzes Pseudobeispiel an. Dieses Mal geht es um ein Pokémon-Thema. Stellen Sie sich vor, wir warten auf ein bestimmtes Pokémon und möchten andere Goroutinen benachrichtigen, wenn es auftaucht.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
In diesem Beispiel wartet eine Goroutine darauf, dass Pikachu auftaucht, während eine andere (der Produzent) zufällig ein Pokémon aus der Liste auswählt und dem Verbraucher signalisiert, wenn ein neues erscheint.
Wenn der Produzent das Signal sendet, wacht der Verbraucher auf und prüft, ob das richtige Pokémon aufgetaucht ist. Wenn ja, fangen wir das Pokémon, wenn nicht, schläft der Verbraucher wieder ein und wartet auf das nächste.
Das Problem besteht darin, dass es eine Lücke zwischen dem Signalgeber des Produzenten und dem tatsächlichen Aufwachen des Verbrauchers gibt. In der Zwischenzeit könnte sich das Pokémon ändern, da die Consumer-Goroutine möglicherweise später als 1 ms aufwacht (selten) oder eine andere Goroutine das gemeinsam genutzte Pokémon ändert. sync.Cond sagt also im Grunde: 'Hey, etwas hat sich geändert!' Wachen Sie auf und schauen Sie sich das an, aber wenn Sie zu spät sind, könnte es sich wieder ändern.'
Wenn der Verbraucher spät aufwacht, läuft das Pokémon möglicherweise weg und die Goroutine schläft wieder ein.
„Huh, ich könnte einen Kanal verwenden, um den Pokémon-Namen oder das Signal an die andere Goroutine zu senden“
Absolut. Tatsächlich werden Kanäle im Allgemeinen gegenüber sync.Cond in Go bevorzugt, da sie einfacher, idiomatischer und den meisten Entwicklern vertraut sind.
Im obigen Fall könnten Sie den Pokémon-Namen einfach über einen Kanal senden oder einfach eine leere Struktur{} zum Signalisieren verwenden, ohne Daten zu senden. Bei unserem Problem geht es jedoch nicht nur darum, Nachrichten über Kanäle weiterzuleiten, sondern auch um den Umgang mit einem gemeinsamen Zustand.
Unser Beispiel ist ziemlich einfach, aber wenn mehrere Goroutinen auf die gemeinsam genutzte Pokemon-Variable zugreifen, schauen wir uns an, was passiert, wenn wir einen Kanal verwenden:
Wenn jedoch mehrere Goroutinen gemeinsam genutzte Daten ändern, ist zum Schutz immer noch ein Mutex erforderlich. In diesen Fällen sehen Sie häufig eine Kombination aus Kanälen und Mutexes, um eine ordnungsgemäße Synchronisierung und Datensicherheit zu gewährleisten.
„Okay, aber was ist mit den Sendesignalen?“
Gute Frage! Sie können tatsächlich ein Broadcast-Signal an alle wartenden Goroutinen, die einen Kanal verwenden, nachahmen, indem Sie ihn einfach schließen (close(ch)). Wenn Sie einen Kanal schließen, werden alle Goroutinen, die von diesem Kanal empfangen, benachrichtigt. Beachten Sie jedoch, dass ein geschlossener Kanal nicht wiederverwendet werden kann. Sobald er geschlossen ist, bleibt er geschlossen.
Übrigens wurde tatsächlich darüber gesprochen, sync.Cond in Go 2 zu entfernen: Vorschlag: sync: den Cond-Typ entfernen.
"Also, wofür ist sync.Cond dann gut?"
Nun, es gibt bestimmte Szenarien, in denen sync.Cond besser geeignet sein kann als Kanäle.
"Warum ist die Sperre in sync.Cond eingebettet?"
Theoretisch muss eine Bedingungsvariable wie sync.Cond nicht an eine Sperre gebunden sein, damit ihre Signalisierung funktioniert.
Sie könnten die Benutzer ihre eigenen Sperren außerhalb der Bedingungsvariablen verwalten lassen, was vielleicht so klingt, als ob es mehr Flexibilität bietet. Es handelt sich nicht wirklich um eine technische Einschränkung, sondern eher um menschliches Versagen.
Eine manuelle Verwaltung kann leicht zu Fehlern führen, da das Muster nicht wirklich intuitiv ist. Sie müssen den Mutex entsperren, bevor Sie Wait() aufrufen, und ihn dann wieder sperren, wenn die Goroutine aufwacht. Dieser Vorgang kann sich umständlich anfühlen und ist ziemlich fehleranfällig, etwa wenn man vergisst, zum richtigen Zeitpunkt zu sperren oder zu entsperren.
Aber warum scheint das Muster etwas abweichend zu sein?
Normalerweise müssen Goroutinen, die cond.Wait() aufrufen, einen gemeinsamen Status in einer Schleife überprüfen, wie folgt:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Die in sync.Cond eingebettete Sperre hilft uns bei der Abwicklung des Sperr-/Entsperrvorgangs und macht den Code sauberer und weniger fehleranfällig. Wir werden das Muster bald im Detail besprechen.
Wenn Sie sich das vorherige Beispiel genau ansehen, werden Sie ein konsistentes Muster im Consumer bemerken: Wir sperren den Mutex immer, bevor wir auf die Bedingung warten (.Wait()), und wir entsperren ihn, nachdem die Bedingung erfüllt ist.
Außerdem packen wir die Wartebedingung in eine Schleife, hier ist eine Auffrischung:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
Wenn wir Wait() auf einer sync.Cond aufrufen, weisen wir die aktuelle Goroutine an, zu warten, bis eine Bedingung erfüllt ist.
Das passiert hinter den Kulissen:
Hier sehen Sie, wie Wait() unter der Haube funktioniert:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Auch wenn es einfach ist, können wir vier Hauptpunkte mitnehmen:
Aufgrund dieses Sperr-/Entsperrverhaltens gibt es ein typisches Muster, dem Sie bei der Verwendung von sync.Cond.Wait() folgen, um häufige Fehler zu vermeiden:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
„Warum nicht einfach c.Wait() direkt ohne Schleife verwenden?“
Dies ist ein Auszug aus dem Beitrag; Der vollständige Beitrag ist hier verfügbar: https://victoriametrics.com/blog/go-sync-cond/
Das obige ist der detaillierte Inhalt vonGo sync.Cond, der am meisten übersehene Synchronisierungsmechanismus. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!