当前位置: 首页 > news >正文

golang sync.Cond - running

golang sync.Cond

 

1 sync.Cond 的使用场景

一句话总结:sync.Cond 条件变量用来协调想要访问共享资源的那些 goroutine,当共享资源的状态发生变化的时候,它可以用来通知被互斥锁阻塞的 goroutine。

sync.Cond 基于互斥锁/读写锁,它和互斥锁的区别是什么呢?

互斥锁 sync.Mutex 通常用来保护临界区和共享资源,条件变量 sync.Cond 用来协调想要访问共享资源的 goroutine。

sync.Cond 经常用在多个 goroutine 等待,一个 goroutine 通知(事件发生)的场景。如果是一个通知,一个等待,使用互斥锁或 channel 就能搞定了。

我们想象一个非常简单的场景:

有一个协程在异步地接收数据,剩下的多个协程必须等待这个协程接收完数据,才能读取到正确的数据。在这种情况下,如果单纯使用 chan 或互斥锁,那么只能有一个协程可以等待,并读取到数据,没办法通知其他的协程也读取数据。

这个时候,就需要有个全局的变量来标志第一个协程数据是否接受完毕,剩下的协程,反复检查该变量的值,直到满足要求。或者创建多个 channel,每个协程阻塞在一个 channel 上,由接收数据的协程在数据接收完毕后,逐个通知。总之,需要额外的复杂度来完成这件事。

Go 语言在标准库 sync 中内置一个 sync.Cond 用来解决这类问题。

package mainimport ("log""sync""sync/atomic""time"
)// done 是一个全局标志,使用原子操作保护,避免数据竞争
// 使用 uint32 类型是因为 atomic 包需要特定的整数类型
var done uint32// read 函数是读者goroutine,等待done标志变为true后执行读取操作
// name: 读者名称,用于日志输出
// cond: 条件变量,用于同步读者和写者
// wg: 等待组,用于等待goroutine完成
func read(name string, cond *sync.Cond, wg *sync.WaitGroup) {defer wg.Done() // 确保goroutine结束时通知等待组
    cond.L.Lock() // 获取条件变量的锁// 使用循环检查条件,防止虚假唤醒// atomic.LoadUint32 原子地读取done的值,保证线程安全for atomic.LoadUint32(&done) == 0 {// cond.Wait() 会暂时释放锁并阻塞,直到被Broadcast唤醒// 唤醒时会重新获取锁,然后继续检查条件
        cond.Wait()}cond.L.Unlock() // 释放锁
    log.Println(name, "reader") // 执行读取操作
}// write 函数是写者goroutine,设置done标志并通知所有读者
// name: 写者名称,用于日志输出
// cond: 条件变量,用于同步读者和写者
// wg: 等待组,用于等待goroutine完成
func write(name string, cond *sync.Cond, wg *sync.WaitGroup) {defer wg.Done() // 确保goroutine结束时通知等待组
    log.Println(name, "starts writing")cond.L.Lock() // 获取条件变量的锁time.Sleep(10 * time.Second) // 模拟写操作耗时// atomic.StoreUint32 原子地设置done为1,保证线程安全// 这会通知所有读者可以开始读取操作atomic.StoreUint32(&done, 1)log.Println(name, "starts end")cond.L.Unlock() // 释放锁// cond.Broadcast() 唤醒所有等待的读者goroutine// 在释放锁后调用Broadcast,避免唤醒的读者立即阻塞在锁上
    cond.Broadcast()
}func main() {// 创建条件变量,需要传入一个互斥锁cond := sync.NewCond(&sync.Mutex{})var wg sync.WaitGroup // 创建等待组用于同步goroutine
    wg.Add(4) // 等待4个goroutine完成:3个读者 + 1个写者// 启动3个读者goroutinego read("reader1", cond, &wg)go read("reader2", cond, &wg)go read("reader3", cond, &wg)// 启动1个写者goroutinego write("writer1", cond, &wg)// 等待所有goroutine完成// 这会阻塞直到所有读者和写者都执行完毕
    wg.Wait()log.Println("All goroutines completed")
}