1.简介
sync.Cond 实现了一个条件变量,在 Locker 的基础上增加了一个消息通知的功能,其内部维护了一个等待队列,队列中存放的是所有等待在这个 sync.Cond 的 Go 程,即保存了一个通知列表。sync.Cond 可以用来唤醒一个或所有因等待条件变量而阻塞的 Go 程,以此来实现多个 Go 程间的同步。sync.Cond 的定义及成员函数如下:
type Cond struct {
// L is held while observing or changing the condition
L Locker
// contains filtered or unexported fields
}
//创建一个带锁的条件变量,Locker 通常是一个 *Mutex 或 *RWMutex
func NewCond(l Locker) *Cond
//唤醒所有因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Broadcast()
//唤醒一个因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Signal()
//自动解锁 c.L 并挂起 goroutine。只有当被 Broadcast 和 Signal 唤醒,Wait 才能返回,返回前会锁定 c.L
func (c *Cond) Wait()
注意:在调用 Signal,Broadcast 之前,应确保目标 Go 程进入 Wait 阻塞状态。
条件变量并不是被用来保护临界区和共享资源的,它是用于协调想要访问共享资源的那些线程的。当共享资源的状态发生变化时,它可以被用来通知被互斥锁阻塞的线程。
条件变量在这里的最大优势就是在效率方面的提升。当共享资源的状态不满足条件的时候,想操作它的线程再也不用循环往复地做检查了,只要等待通知就好了。
例如同学聚会在 KTV 包间唱歌,假如只有一个麦克风,因为麦克风这类共享资源同一时间内只允许一个人使用,当你的同桌正在拿着麦克风唱“同桌的你”,此时你想唱“老男孩”,那么你的选择可以是:
(1)一直盯看着他,发现他唱完了,立马接过麦克风;
(2)找个地方静静等待(虽然啥也做不了,但不用盯着),等他用完后告诉你一声。
2.要点
(1)Cond 不能被复制。
因为 Cond 内部维护着一个所有等待在这个 Cond 的 Go 程队列,如果 Cond 允许值传递,则这个队列在值传递的过程中会进行复制,导致在唤醒 Go 程的时候出现错误。
(2)唤醒顺序。
从等待队列中按照顺序唤醒,先进入等待队列,先被唤醒。
3.使用示例
package main
import (
"fmt"
"sync"
"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
func main() {
for i := 0; i < 10; i++ {
go func(x int) {
cond.L.Lock() //获取锁
defer cond.L.Unlock() //释放锁
cond.Wait() //等待通知,阻塞当前 goroutine
// do something. 这里仅打印
fmt.Println(x)
}(i)
}
time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 进入 Wait 阻塞状态
fmt.Println("Signal...")
cond.Signal() // 1 秒后下发一个通知给已经获取锁的 goroutine
time.Sleep(time.Second * 1)
fmt.Println("Signal...")
cond.Signal() // 1 秒后下发下一个通知给已经获取锁的 goroutine
time.Sleep(time.Second * 1)
cond.Broadcast() // 1 秒后下发广播给所有等待的goroutine
fmt.Println("Broadcast...")
time.Sleep(time.Second * 1) // 睡眠 1 秒,等待所有 goroutine 执行完毕
}
多次运行结果不一致,示例输出:
Signal...
4
Signal...
0
Broadcast...
7
1
2
3
5
9
6
8
注意:
(1)调用 Wait() 函数前,需要先获得条件变量的成员锁,原因是需要互斥地变更条件变量的等待队列。在 Wait() 返回前,会重新上锁。
(2)条件变量和锁结合使用,在并发时如果逻辑不严谨容易发生死锁,所以尽量不要使用条件变量,推荐用 sync.WaitGroup 来实现并发时 Go 程间的同步。
参考文章
[1] Package sync.Cond
[2] Golang Cond源码分析
[3] GO 条件锁sync.Cond(1)
[4] GO 条件锁sync.Cond(2)
来源:CSDN
作者:Dablelv
链接:https://blog.csdn.net/K346K346/article/details/95673050