最近在学习Go语言高性能编程 时看到了sync.Cond
条件变量这个概念,一时有些难以理解。查阅资料后对其有了些认知,特此记录。
概念 sync
包提供了条件变量类型sync.Cond
,它可以和互斥锁或读写锁组合使用,用来协调想要访问共享变量的协程。其主要作用机制是在对应的共享资源状态发生变化时,通知其它因此而阻塞的协程。
定义 sync.Cond
是一个结构体,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Cond struct { noCopy noCopy L Locker notify notifyList checker copyChecker }
该类型可通过sync
包中的func NewCond(l Locker) *Cond
方法创建。
它有如下三个方法:
应用场景 由以上描述可知,该结构的主要用法应该是多个协程调用Wait
等待某个协程运行,该协程任务完毕后调用Broadcast
或Signal
将等待的协程唤醒。即:多用在多个协程等待,一个协程通知的场景 。
举个生活中的小例子:
多位同学到达食堂,此时食堂还未做完饭,同学们需要等待食堂出餐后才能恰饭。
用Go
条件变量对以上场景进行描述,可写出如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport ( "fmt" "sync" "time" ) var ( ok = false food = "" rwMutex = &sync.RWMutex{} cond = sync.NewCond(rwMutex.RLocker()) ) func makeFood () { rwMutex.Lock() defer rwMutex.Unlock() fmt.Println("食堂开始做饭!" ) time.Sleep(3 * time.Second) ok = true food = "鱼香肉丝" fmt.Println("食堂做完饭了!" ) cond.Broadcast() } func waitToEat () { cond.L.Lock() defer cond.L.Unlock() for !ok { cond.Wait() } fmt.Println("总算吃到饭了,这顿吃的是" , food) } func main () { for i := 0 ; i < 3 ; i++ { go waitToEat() } go makeFood() time.Sleep(4 * time.Second) }
运行该程序,结果如下:
1 2 3 4 5 食堂开始做饭! 食堂做完饭了! 总算吃到饭了,这顿吃的是 鱼香肉丝 总算吃到饭了,这顿吃的是 鱼香肉丝 总算吃到饭了,这顿吃的是 鱼香肉丝
与其它概念的区别 channel 众所周知,channel
同样是 Go 中用于协程同步的概念,但与sync.Cond
不同,channel
多用于一对一通信,如果继承上面的例子,这次的场景应该是:
一位同学到达食堂,此时食堂还未做完饭,该同学需要等待食堂出餐后才能恰饭。
用channel
描述为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "time" ) var ( food = "" ch = make (chan struct {}) ) func makeFood () { fmt.Println("食堂开始做饭!" ) time.Sleep(3 * time.Second) food = "鱼香肉丝" fmt.Println("食堂做完饭了!" ) ch <- struct {}{} } func waitToEat () { <-ch fmt.Println("总算吃到饭了,这顿吃的是" , food) } func main () { go waitToEat() go makeFood() time.Sleep(4 * time.Second) }
运行结果为:
1 2 3 食堂开始做饭! 食堂做完饭了! 总算吃到饭了,这顿吃的是 鱼香肉丝
sync.WaitGroup sync.WaitGroup
同样用于协程同步,但应用场景与sync.Cond
刚好相反,后者多用于多协程等待,单协程通知 ,而前者多用于单协程等待多协程执行完毕 。
仍然使用上述例子:
一位同学到达食堂,此时食堂多个窗口均未做完饭,该同学想要等待每个窗口都做完饭后各点一份吃。(多少有点离谱了XD)
代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "sync" "time" ) var ( wg sync.WaitGroup ) func makeFood () { fmt.Println("窗口开始做饭!" ) time.Sleep(3 * time.Second) fmt.Println("窗口做完饭了!" ) wg.Done() } func waitToEat () { wg.Wait() fmt.Println("oho,每个窗口来一份!" ) } func main () { for i := 0 ; i < 3 ; i++ { wg.Add(1 ) go makeFood() } go waitToEat() time.Sleep(4 * time.Second) }
运行结果:
1 2 3 4 5 6 7 窗口开始做饭! 窗口开始做饭! 窗口开始做饭! 窗口做完饭了! 窗口做完饭了! 窗口做完饭了! oho,每个窗口来一份!
结语 以上为个人的浅薄理解,如有错误欢迎在评论区指出,感谢!