Einführung Ich möchte den Typ und die Anwendungsfälle von sowie dessen Einsatzzweck besprechen. sync.Cond Was ist ? sync.Cond In der Programmiersprache Go ist ein im Paket definierter Typ, der eine Bedingungsvariable darstellt. Bedingungsvariablen sind Synchronisierungsprimitive, die zur Koordination von Goroutinen verwendet werden, indem sie es ihnen ermöglichen, zu warten, bis eine bestimmte Bedingung erfüllt ist, bevor sie fortfahren. sync.Cond sync Der Typ bietet eine Möglichkeit zum Erstellen und Verwalten von Bedingungsvariablen. Er verfügt über drei Hauptmethoden: sync.Cond : Diese Methode bewirkt, dass die aufrufende Goroutine wartet, bis eine andere Goroutine die Bedingungsvariable signalisiert. Wenn die Goroutine aufruft, gibt sie die zugehörige Sperre frei und unterbricht die Ausführung, bis eine andere Goroutine oder für dieselbe Variable aufruft. Wait() Wait() Signal() Broadcast() sync.Cond : Diese Methode weckt eine Goroutine, die auf die Bedingungsvariable wartet. Wenn mehrere Goroutinen warten, wird nur eine davon geweckt. Die Wahl, welche Goroutine geweckt wird, ist willkürlich und nicht garantiert. Signal() : Diese Methode weckt alle Goroutinen, die auf die Bedingungsvariable warten. Wenn aufgerufen wird, werden alle wartenden Goroutinen geweckt und können fortfahren. Broadcast() Broadcast() Beachten Sie, dass ein zugehöriges erfordert, um den Zugriff auf die Bedingungsvariable zu synchronisieren. sync.Cond sync.Mutex Durch die Verwendung von können Sie die Ausführung von Goroutinen basierend auf bestimmten Bedingungen koordinieren und so eine kontrolliertere und synchronisiertere gleichzeitige Programmierung in Go ermöglichen. sync.Cond Häufige Anwendungsfälle wird häufig in Szenarien verwendet, in denen Goroutinen basierend auf bestimmten Bedingungen koordiniert werden und miteinander kommunizieren müssen. Betrachten wir gängige Anwendungsfälle für . sync.Cond sync.Cond Goroutine-Synchronisierung kann verwendet werden, um die Ausführung mehrerer Goroutinen zu synchronisieren. Sie haben beispielsweise mehrere Goroutinen, die warten müssen, bis eine bestimmte Bedingung erfüllt ist, bevor sie fortfahren können. Die wartenden Goroutinen können aufrufen, und die signalisierende Goroutine kann oder aufrufen, um die wartenden Goroutinen aufzuwecken, wenn die Bedingung erfüllt ist. sync.Cond cond.Wait() cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(2) go func() { fmt.Println("Goroutine 1 is started") defer wg.Done() cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 1 is waiting for condition") cond.Wait() fmt.Println("Goroutine 1 met the condition") fmt.Println("Goroutine 1 is done") }() go func() { fmt.Println("Goroutine 2 is started") defer wg.Done() time.Sleep(5 * time.Second) // Simulating some work cond.L.Lock() defer cond.L.Unlock() fmt.Println("Goroutine 2 is signaling condition") cond.Signal() fmt.Println("Goroutine 2 completed signaling") fmt.Println("Goroutine 2 is done") }() wg.Wait() } In diesem Beispiel haben wir zwei Goroutinen. Die erste Goroutine wartet mit auf eine Bedingung, während die zweite Goroutine die Bedingung mit signalisiert. cond.Wait() cond.Signal() Wenn das Programm ausgeführt wird, erhält die erste Goroutine die Sperre und ruft dann auf. Da die Bedingung noch nicht erfüllt ist, gibt die erste Goroutine die Sperre frei und unterbricht ihre Ausführung. cond.Wait() In der Zwischenzeit schläft die zweite Goroutine fünf Sekunden lang und simuliert einige Arbeiten. Sie erhält die Sperre und ruft dann auf. Sie weckt die wartende Goroutine, die dann die Sperre erhält und ausgeführt wird. cond.Signal() Die Verwendung von stellt sicher, dass die erste Goroutine wartet, bis die zweite Goroutine die Bedingung signalisiert, wodurch eine Synchronisierung und Koordination zwischen den beiden Goroutinen ermöglicht wird. sync.Cond Produzent-Konsumenten-Problem kann bei der Lösung des nützlich sein, einem klassischen Synchronisationsproblem, bei dem zwei Arten von Prozessen beteiligt sind, Erzeuger und Verbraucher, die sich einen gemeinsamen Puffer oder eine Warteschlange mit fester Größe teilen. Die Erzeuger-Goroutinen können oder verwenden, um die Verbraucher-Goroutinen zu benachrichtigen, wenn neue Daten zum Verbrauch verfügbar sind. sync.Cond Erzeuger-Verbraucher-Problems cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const MaxMessageChannelSize = 5 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) messageChannel := NewMessageChannel(MaxMessageChannelSize) producer := NewProducer(cond, messageChannel) consumer := NewConsumer(cond, messageChannel) wg.Add(2) go func() { defer wg.Done() for i := range 10 { producer.Produce(fmt.Sprintf("Message %d", i)) } }() go func() { defer wg.Done() for range 10 { consumer.Consume() } }() wg.Wait() } type MessageChannel struct { maxBufferSize int buffer []string } func NewMessageChannel(size int) *MessageChannel { return &MessageChannel{ maxBufferSize: size, buffer: make([]string, 0, size), } } func (mc *MessageChannel) IsEmpty() bool { return len(mc.buffer) == 0 } func (mc *MessageChannel) IsFull() bool { return len(mc.buffer) == mc.maxBufferSize } func (mc *MessageChannel) Add(message string) { mc.buffer = append(mc.buffer, message) } func (mc *MessageChannel) Get() string { message := mc.buffer[0] mc.buffer = mc.buffer[1:] return message } type Producer struct { cond *sync.Cond messageChannel *MessageChannel } func NewProducer(cond *sync.Cond, messageChannel *MessageChannel) *Producer { return &Producer{ cond: cond, messageChannel: messageChannel, } } func (p *Producer) Produce(message string) { time.Sleep(500 * time.Millisecond) // Simulating some work p.cond.L.Lock() defer p.cond.L.Unlock() for p.messageChannel.IsFull() { fmt.Println("Producer is waiting because the message channel is full") p.cond.Wait() } p.messageChannel.Add(message) fmt.Println("Producer produced the message:", message) p.cond.Signal() } type Consumer struct { id int cond *sync.Cond messageChannel *MessageChannel } func NewConsumer(cond *sync.Cond, messageChannel *MessageChannel) *Consumer { return &Consumer{ cond: cond, messageChannel: messageChannel, } } func (c *Consumer) Consume() { time.Sleep(1 * time.Second) // Simulating some work c.cond.L.Lock() defer c.cond.L.Unlock() for c.messageChannel.IsEmpty() { fmt.Println("Consumer is waiting because the message channel is empty") c.cond.Wait() } message := c.messageChannel.Get() fmt.Println("Consumer consumed the message:", message) c.cond.Signal() } In diesem Beispiel haben wir eine Produzenten-Goroutine, die Nachrichten produziert und sie dem Nachrichtenkanal hinzufügt, und eine Konsumenten-Goroutine, die Nachrichten konsumiert. Der Nachrichtenkanal hat eine maximale Größe, die durch definiert ist. MaxMessageChannelSize Die Produzenten-Goroutine fügt Nachrichten zum Nachrichtenkanal hinzu und verwendet , um die Konsumenten-Goroutine zu benachrichtigen, wenn neue Daten verfügbar sind. Wenn der Nachrichtenkanal voll ist, wartet die Produzenten-Goroutine mit , bis der Konsument einige Daten verbraucht und Platz im Nachrichtenkanal freigibt. cond.Signal() cond.Wait() In ähnlicher Weise konsumiert die Consumer-Goroutine Nachrichten aus dem Nachrichtenkanal und verwendet um die Producer-Goroutine zu benachrichtigen, wenn im Nachrichtenkanal Platz frei wird. Wenn dieser leer ist, wartet die Consumer-Goroutine mit , bis der Producer Daten produziert und sie dem Nachrichtenkanal hinzufügt. cond.Signal() cond.Wait() Hier ermöglicht die Koordination und Synchronisierung zwischen den Goroutinen des Produzenten und Konsumenten. Es stellt sicher, dass der Konsument wartet, wenn der Nachrichtenkanal leer ist, und der Produzent wartet, wenn er voll ist, wodurch das Produzenten-Konsumenten-Problem gelöst wird. sync.Cond Ressourcensynchronisierung Angenommen, mehrere Goroutinen benötigen exklusiven Zugriff auf eine gemeinsam genutzte Ressource. kann verwendet werden, um den Zugriff zu koordinieren. Beispielsweise muss ein Pool von Worker-Goroutinen möglicherweise warten, bis eine bestimmte Anzahl von Ressourcen verfügbar ist, bevor sie mit der Verarbeitung beginnen können. Die Goroutinen können mit auf die Bedingungsvariable warten und mit oder über die Freigabe von Ressourcen benachrichtigen. sync.Cond cond.Wait() cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const MaxResources = 3 func main() { var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) resourceProvider := NewResourceProvider(cond, MaxResources) wg.Add(10) for i := range 10 { go func(workerID int) { defer wg.Done() worker := NewWorker(workerID, cond, resourceProvider) worker.Run() }(i) } wg.Wait() } type ResourceProvider struct { maxResources int availableResources int cond *sync.Cond } func NewResourceProvider(cond *sync.Cond, maxResources int) *ResourceProvider { return &ResourceProvider{ cond: cond, availableResources: maxResources, } } func (rp *ResourceProvider) AvailableResources() int { return rp.availableResources } func (rp *ResourceProvider) AcquireResoirce() { rp.availableResources-- } func (rp *ResourceProvider) ReleaseResource() { rp.availableResources++ } type Worker struct { id int cond *sync.Cond rp *ResourceProvider } func NewWorker(workerID int, cond *sync.Cond, rp *ResourceProvider) *Worker { return &Worker{ id: workerID, cond: cond, rp: rp, } } func (w *Worker) Run() { w.cond.L.Lock() for w.rp.AvailableResources() == 0 { fmt.Printf("Worker %d is waiting for resources\n", w.id) w.cond.Wait() } w.rp.AcquireResoirce() fmt.Printf("Worker %d acquired resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.L.Unlock() time.Sleep(1 * time.Second) // Simulating work w.cond.L.Lock() defer w.cond.L.Unlock() w.rp.ReleaseResource() fmt.Printf("Worker %d released resource. Remaining resources: %d\n", w.id, w.rp.AvailableResources()) w.cond.Signal() } In diesem Beispiel haben wir mehrere Worker-Goroutinen, die exklusiven Zugriff auf begrenzte Ressourcen benötigen. Die Worker-Goroutinen beschaffen und geben Ressourcen frei, indem sie verwenden, um sich mit anderen Workern abzustimmen. Wenn keine Ressourcen verfügbar sind, warten die Worker-Goroutinen , bis die andere Goroutine die Ressource freigibt. cond.Signal() using cond.Wait() In diesem Beispiel ermöglicht die Synchronisierung und Koordination zwischen den Worker-Goroutinen und stellt sicher, dass die Worker-Goroutinen warten, wenn keine Ressourcen verfügbar sind, wodurch der Ressourcenzugriff effektiv synchronisiert wird. sync.Cond Ereignisbenachrichtigung kann verwendet werden, um Goroutinen über bestimmte Ereignisse oder Änderungen im System zu benachrichtigen. Sie können beispielsweise Goroutinen auf ein bestimmtes Ereignis warten lassen. Wenn das Ereignis eintritt, kann die signalisierende Goroutine oder verwenden, um die wartenden Goroutinen aufzuwecken und ihnen zu ermöglichen, das Ereignis zu verarbeiten. sync.Cond cond.Signal() cond.Broadcast() package main import ( "fmt" "sync" "time" ) const maxWorkersCount = 10 func main() { var counter int32 var wg sync.WaitGroup var mu sync.Mutex cond := sync.NewCond(&mu) wg.Add(maxWorkersCount) for i := range maxWorkersCount { go func(workerID int) { defer wg.Done() fmt.Printf("Worker %d performing work\n", workerID) time.Sleep(1 * time.Second) // Simulate work cond.L.Lock() defer cond.L.Unlock() counter++ if counter == maxWorkersCount { fmt.Println("All workers have reached the barrier") cond.Broadcast() } else { fmt.Printf("Worker %d is waiting at the barrier\n", workerID) cond.Wait() } fmt.Printf("Worker %d passed the barrier\n", workerID) }(i) } wg.Wait() } Hier haben wir mehrere Worker-Goroutinen, die an einem Barrierepunkt arbeiten und synchronisieren. Die Worker-Goroutinen erhöhen einen Zähler und warten dann entweder an der Barriere oder signalisieren der Barriere mit und basierend auf der Anzahl der Worker, die die Barriere erreichen. cond.Wait() cond.Broadcast() Jede Worker-Goroutine führt eine Arbeit aus und erhält dann die Sperre, um die Zählervariable zu erhöhen. Wenn der aktuelle Worker als letzter die Barriere erreicht, sendet er den Barrierezustand mit um alle wartenden Worker aufzuwecken. Andernfalls wartet er mit an der Barriere, um vom letzten Worker benachrichtigt zu werden. cond.Broadcast() cond.Wait() Die Barrierensynchronisierung stellt sicher, dass alle Worker-Goroutinen die Barriere erreichen, bevor eine von ihnen darüber hinausgeht. Dies kann in Szenarien nützlich sein, in denen die Ausführung mehrerer Goroutinen an einem bestimmten Punkt in ihrem Workflow synchronisiert werden muss. Beachten Sie, dass die Barriere in diesem Beispiel mithilfe eines einfachen Zählers implementiert wird. In komplexeren Szenarien müssen Sie jedoch möglicherweise zusätzliche Synchronisierungsmechanismen oder -bedingungen berücksichtigen, um eine korrekte Synchronisierung sicherzustellen und Race Conditions zu vermeiden. Abschluss Zusammenfassend lässt sich sagen, dass ein nützlicher Typ in der Programmiersprache Go ist, der die Synchronisierung und Koordination zwischen Goroutinen basierend auf bestimmten Bedingungen ermöglicht. Er bietet eine Möglichkeit, Bedingungsvariablen zu erstellen und zu verwalten. Er verfügt über Methoden zum Warten auf, Signalisieren und Senden von Bedingungen. Durch die Verwendung von „sync.Cond“ können Sie kontrolliertere und synchronisiertere parallele Programme in Go schreiben. sync.Cond Es ist wichtig zu beachten, dass nur eines der Synchronisationsprimitive ist, die von der Go-Standardbibliothek bereitgestellt werden, und dass seine Verwendung von den spezifischen Anforderungen Ihres parallelen Programms abhängt. In einigen Fällen sind andere Synchronisationsprimitive wie Kanäle oder möglicherweise besser geeignet. sync.Cond sync.WaitGroup