Cond 条件变量
- 主要是用于多个
goroutine
之间进行通知 - 场景:等待某个事件的发生、通知某个事件发生
内部数据结构
go
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
-
noCopy
防止拷贝的一个标识 -
L
实现的一个互斥锁的接口 -
notify
阻塞链表gotype notifyList struct { wait uint32 notify uint32 lock uintptr // key field of the mutex head unsafe.Pointer tail unsafe.Pointer }
wait
总需要等待的数量notify
已经通知的数量lock
阻塞链表的内部锁head
链表头部指针tail
链表尾部指针
核心方法
-
Wait
:释放锁,挂起(阻塞)当前的goroutine
,直到被唤醒,唤醒后再次获取锁gofunc (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() }
- 先进行一个
check ckeck
的操作,检查是否被copy - 将阻塞链表
notifyList
中的wait
统计数+1,获取到一个"票" - 将当前
goroutine
包装成一个节点,添加到notifyList
链表的尾部 - 释放所(重点要考)
- 调用系统内部
gopark
将当前goroutine
挂起 - 然后加锁。结束
- 注意 :因为wait方法中先进行了释放锁的操作,所以在调用wait方法的时候一定要先进行加锁操作。不然会直接报
panic
- 先进行一个
-
Singal
:唤醒一个等待者gofunc (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) }
- 依旧先进行一个
check ckeck
的操作,检查是否被copy - 阻塞链表
notifyList
中notify
统计数+1 - 从头开始遍历阻塞链表,调用内部
goready
唤醒一个等待时间最长的goroutine
- 依旧先进行一个
-
Broadcast
:唤醒所有的等待者gofunc (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
- 依旧先进行一个
check ckeck
的操作,检查是否被copy - 将
wait
赋值给notify
- 唤醒所有阻塞链表中的节点
- 依旧先进行一个
简单的使用方法:
go
func Test_Cond(t *testing.T) {
var c = sync.NewCond(new(sync.Mutex))
var count int
for i := 0; i < 10; i++ {
go func(number int) {
time.Sleep(1 * time.Second)
c.L.Lock()
count++
c.L.Unlock()
// 通知
log.Printf("通知唤醒:i: %d count: %d", number, count)
c.Signal()
}(i)
}
c.L.Lock()
for count != 10 {
c.Wait()
log.Printf("唤醒:count: %d", count)
}
c.L.Unlock()
logx.Info("done")
}
大致简单的使用方法就是这样,一定要注意 在使用wait
方法的时候一定要先进行加锁操作,还需要判断达成条件。