paint-brush
理解 Go 中的 sync.Cond:初学者指南by@ivanlemeshev
1,857
1,857

理解 Go 中的 sync.Cond:初学者指南

Ivan Lemeshev9m2024/04/28
Read on Terminal Reader

总之,sync.Cond 是 Go 编程语言中一个有用的类型,它允许根据特定条件在 goroutine 之间进行同步和协调。它提供了一种创建和管理条件变量的方法。它具有等待、信号和广播条件的方法。通过使用 `sync.Cond`,您可以在 Go 中编写更可控和同步的并发程序。 需要注意的是,sync.Cond 只是 Go 标准库提供的同步原语之一,其使用取决于并发程序的特定要求。在某些情况下,其他同步原语(如通道或 sync.WaitGroup)可能更合适。
featured image - 理解 Go 中的 sync.Cond:初学者指南
Ivan Lemeshev HackerNoon profile picture
0-item

介绍

我想讨论sync.Cond类型和用例以及何时使用它。

什么是sync.Cond

在 Go 编程语言中, sync.Condsync包中定义的一个类型,表示条件变量。条件变量是用于协调 goroutine 的同步原语,允许它们等待特定条件变为真后再继续执行。


sync.Cond类型提供了一种创建和管理条件变量的方法。它有三种主要方法:


  1. Wait() :此方法使调用 goroutine 等待,直到另一个 goroutine 向条件变量发出信号。当 goroutine 调用Wait()时,它会释放关联的锁并暂停执行,直到另一个 goroutine 在同一个sync.Cond变量上调用Signal()Broadcast()


  2. Signal() :此方法唤醒一个等待条件变量的 goroutine。如果有多个 goroutine 正在等待,则只会唤醒其中一个。选择唤醒哪个 goroutine 是任意的,并且不保证。


  3. Broadcast() :此方法唤醒所有等待条件变量的 goroutine。调用Broadcast()时,所有等待的 goroutine 都会被唤醒并可以继续执行。


请注意, sync.Cond需要关联的sync.Mutex来同步对条件变量的访问。


通过使用sync.Cond ,您可以根据特定条件协调 goroutine 的执行,从而允许在 Go 中进行更可控和同步的并发编程。

常见用例

sync.Cond通常用于 goroutine 需要根据特定条件相互协调和通信的场景。让我们考虑一下sync.Cond的常见用例。

Goroutine 同步

sync.Cond可用于同步多个 goroutine 的执行。例如,您可能有多个 goroutine,它们必须等待特定条件满足后才能继续执行。等待的 goroutine 可以调用cond.Wait() ,而信号 goroutine 可以调用cond.Signal()cond.Broadcast()以在条件满足时唤醒等待的 goroutine。


 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() }


在这个例子中,我们有两个 goroutine。第一个 goroutine 使用cond.Wait()等待条件,而第二个 goroutine 使用cond.Signal()发出条件信号。


当程序执行时,第一个 goroutine 获取锁,然后调用cond.Wait() 。由于条件尚未满足,第一个 goroutine 释放锁并暂停其执行。


同时,第二个 goroutine 休眠五秒钟,模拟一些工作。它获取锁,然后调用cond.Signal() 。它唤醒等待的 goroutine,然后获取锁并执行。


sync.Cond的使用确保第一个 goroutine 等待直到第二个 goroutine 发出条件信号,从而实现两个 goroutine 之间的同步和协调。

生产者-消费者问题

sync.Cond可用于解决生产者-消费者问题,这是一个经典的同步问题,涉及两种类型的进程,即生产者和消费者,它们共享一个固定大小的缓冲区或队列。当有新数据可供使用时,生产者 goroutine 可以使用cond.Signal()cond.Broadcast()通知消费者 goroutine。


 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() }


在这个例子中,我们有一个生产者 goroutine,它生产消息并将其添加到消息通道,还有一个消费者 goroutine,它消费消息。消息通道的最大大小由MaxMessageChannelSize定义。


生产者 goroutine 将消息添加到消息通道,并使用cond.Signal()在有新数据可用时通知消费者 goroutine。如果消息通道已满,生产者 goroutine 将使用cond.Wait()等待,直到消费者消费一些数据并释放消息通道中的空间。


类似地,消费者 goroutine 从消息通道消费消息,并使用cond.Signal()在消息通道有可用空间时通知生产者 goroutine。如果通道为空,消费者 goroutine 将使用cond.Wait()等待,直到生产者生成一些数据并将其添加到消息通道。


这里, sync.Cond实现了生产者和消费者 goroutine 之间的协调和同步,保证了当消息通道为空时,消费者等待,当通道为满时,生产者等待,从而解决了生产者-消费者问题。

资源同步

假设多个 goroutine 需要独占访问共享资源。 sync.Cond可用于协调访问。 例如,一组工作 goroutine 可能需要等到一定数量的资源可用后才能开始处理。 goroutine 可以使用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() }


在此示例中,我们有多个工作 goroutine,它们需要独占访问有限的资源。工作 goroutine 使用cond.Signal()获取和释放资源,以便与其他工作程序进行协调。如果没有可用资源,工作 goroutine 将using cond.Wait()等待,直到其他 goroutine 释放资源。


在这个例子中, sync.Cond允许工作 goroutine 之间进行同步和协调,确保工作 goroutine 在没有可用资源时进行等待,从而有效地同步资源访问。

事件通知

sync.Cond可用于通知 goroutine 有关系统中的特定事件或更改。例如,您可以让 goroutine 等待特定事件。当事件发生时,信号 goroutine 可以使用cond.Signal()cond.Broadcast()唤醒等待的 goroutine 并允许它们处理事件。


 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() }


这里,我们有多个工作 goroutine 执行工作并在屏障点同步。工作 goroutine 增加计数器,然后根据到达屏障的工作器数量,在屏障处等待或使用cond.Wait()cond.Broadcast()屏障发出信号。


每个工作 goroutine 执行一些工作,然后获取锁以增加计数器变量。 如果当前工作程序是最后一个到达屏障的工作程序,它会使用cond.Broadcast()广播屏障条件以唤醒所有等待的工作程序。 否则,它会使用cond.Wait()在屏障处等待,以接收最后一个工作程序的通知。


屏障同步可确保所有工作 goroutine 在任何 goroutine 超越屏障之前都到达屏障。在需要在工作流程中的特定点同步多个 goroutine 执行的场景中,它非常有用。


请注意,本例中屏障是使用简单计数器实现的。然而,在更复杂的场景中,您可能需要考虑额外的同步机制或条件,以确保正确同步并避免竞争条件。

结论

总之, sync.Cond是 Go 编程语言中一个有用的类型,它允许根据特定条件在 goroutine 之间进行同步和协调。它提供了一种创建和管理条件变量的方法。它具有等待、信号和广播条件的方法。通过使用 `sync.Cond`,您可以在 Go 中编写更受控制和同步的并发程序。


需要注意的是, sync.Cond只是 Go 标准库提供的同步原语之一,其使用取决于并发程序的具体要求。在某些情况下,其他同步原语(如通道或sync.WaitGroup可能更合适。