内容简介:Golang的sync包中的Cond实现了一种条件变量,可以使用在多个Reader等待共享资源ready的场景(如果只有一读一写,一个锁或者channel就搞定了)。Cond的汇合点:多个goroutines等待、1个goroutine通知事件发生。每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex),当修改条件或者调用Wait方法时,必须加锁,
Golang的sync包中的Cond实现了一种条件变量,可以使用在多个Reader等待共享资源ready的场景(如果只有一读一写,一个锁或者channel就搞定了)。
Cond的汇合点:多个goroutines等待、1个goroutine通知事件发生。
每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex),当修改条件或者调用Wait方法时,必须加锁, 保护condition 。
type Cond struct { // L is held while observing or changing the condition L Locker // contains filtered or unexported fields }
NewCond
func NewCond(l Locker) *Cond
新建一个Cond条件变量。
Broadcast
func (c *Cond) Broadcast()
Broadcast会唤醒 所有 等待c的goroutine。
调用Broadcast的时候,可以加锁,也可以不加锁。
Signal
func (c *Cond) Signal()
Signal只唤醒 1个 等待c的goroutine。
调用Signal的时候,可以加锁,也可以不加锁。
Wait
func (c *Cond) Wait()
Wait()
会自动释放 c.L
,并挂起调用者的goroutine。之后恢复执行, Wait()
会在返回时对 c.L
加锁。
除非被Signal或者Broadcast唤醒,否则 Wait()
不会返回。
由于 Wait()
第一次恢复时, C.L
并没有加锁,所以当Wait返回时,调用者通常并不能假设条件为真。
取而代之的是, 调用者应该在循环中调用Wait。(简单来说,只要想使用condition,就必须加锁。)
c.L.Lock() for !condition() { c.Wait() } ... make use of condition ... c.L.Unlock()
举个例子
下面这个例子,可以比较好的说明Cond的使用方法。
package main import ( "fmt" "sync" "time" ) var sharedRsc = false func main() { var wg sync.WaitGroup wg.Add(2) m := sync.Mutex{} c := sync.NewCond(&m) go func() { // this go routine wait for changes to the sharedRsc c.L.Lock() for sharedRsc == false { fmt.Println("goroutine1 wait") c.Wait() } fmt.Println("goroutine1", sharedRsc) c.L.Unlock() wg.Done() }() go func() { // this go routine wait for changes to the sharedRsc c.L.Lock() for sharedRsc == false { fmt.Println("goroutine2 wait") c.Wait() } fmt.Println("goroutine2", sharedRsc) c.L.Unlock() wg.Done() }() // this one writes changes to sharedRsc time.Sleep(2 * time.Second) c.L.Lock() fmt.Println("main goroutine ready") sharedRsc = true c.Broadcast() fmt.Println("main goroutine broadcast") c.L.Unlock() wg.Wait() }
执行结果如下。
goroutine1 wait goroutine2 wait main goroutine ready main goroutine broadcast goroutine2 true goroutine1 true
goroutine1和goroutine2进入Wait状态,在main goroutine在2s后资源满足,发出broadcast信号后,从Wait中恢复并判断条件是否确实已经满足(sharedRsc不为空),满足则消费条件,并解锁、 wg.Done()
。
修改1
我们做个修改,删除main goroutine中的2s延时。
代码就不贴了。
执行结果如下。
main goroutine ready main goroutine broadcast goroutine2 true goroutine1 true
很有意思,两个goroutine都没有进入Wait状态。
原因是,main goroutine执行的更快,在goroutine1/goroutine2加锁之前就已经获得了锁,并完成了修改sharedRsc、发出Broadcast信号。
当子goroutine调用Wait之前检验condition时,条件已经满足,因此就没有必要再去调用Wait了。
修改2
如果我们在子goroutine中不做校验呢?
我们会得到1个死锁。
main goroutine ready main goroutine broadcast goroutine2 wait goroutine1 true fatal error: all goroutines are asleep - deadlock! goroutine 1 [semacquire]: sync.runtime_Semacquire(0x414028, 0x19) /usr/local/go/src/runtime/sema.go:56 +0x40 sync.(*WaitGroup).Wait(0x414020, 0x40c108) /usr/local/go/src/sync/waitgroup.go:130 +0x60 main.main() /tmp/sandbox947808816/prog.go:44 +0x2c0 goroutine 6 [sync.Cond.Wait]: runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:307 sync.runtime_notifyListWait(0x43e268, 0x0) /usr/local/go/src/runtime/sema.go:510 +0x120 sync.(*Cond).Wait(0x43e260, 0x40c108) /usr/local/go/src/sync/cond.go:56 +0xe0 main.main.func2(0x43e260, 0x414020) /tmp/sandbox947808816/prog.go:31 +0xc0 created by main.main /tmp/sandbox947808816/prog.go:27 +0x140
为什么呢?
main goroutine(goroutine 1)先执行,并停留在 wg.Wait()中,等待子goroutine的wg.Done();而子goroutine(goroutine 6)没有判断条件直接调用了cond.Wait。
我们知道cond.Wait会释放锁并等待其他goroutine调用Broadcast或者Signal来通知其恢复执行,除此之外没有其他的恢复途径。但此时main goroutine已经调用了Broadcast并进入了等待状态,没有任何goroutine会去拯救还在cond.Wait中的子goroutine了,而该子goroutine也没有机会调用wg.Done()去恢复main goroutine,造成了死锁。
因此,一定要注意,Broadcast必须要在所有的Wait之后(当然了,可以通过条件判断来决定要不要进Wait)。
一个真实的例子
我们来看看k8s中使用Cond实现的 FIFO ,它是如何处理条件的消费的。
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) { f.lock.Lock() defer f.lock.Unlock() for { for len(f.queue) == 0 { // When the queue is empty, invocation of Pop() is blocked until new item is enqueued. // When Close() is called, the f.closed is set and the condition is broadcasted. // Which causes this loop to continue and return from the Pop(). if f.IsClosed() { return nil, FIFOClosedError } f.cond.Wait() } id := f.queue[0] f.queue = f.queue[1:] ... } } func NewFIFO(keyFunc KeyFunc) *FIFO { f := &FIFO{ items: map[string]interface{}{}, queue: []string{}, keyFunc: keyFunc, } f.cond.L = &f.lock return f }
Cond共用了FIFO的lock,在Pop时,会先加锁 f.lock.Lock()
,而在 f.cond.Wait()
前,会先检查 len(f.queue)
是否为0,防止2种情况:
- 如上面的例子3,条件已经满足,不需要wait
-
唤醒时满足,但被其他goroutine捷足先登,阻塞在
f.lock
的加锁中;当获得了锁,加锁成功以后,f.queue
已经被消费为空,直接访问f.queue[0]
会访问越界。
Ref:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
CSS实战手册(第2版)
[美] David Sawyer McFarland / 俞黎敏 / 电子工业出版社 / 2010-6 / 69.80元
本书从介绍最基本的CSS知识开始,到建立用于打印网页的CSS和改进你的CSS习惯的最佳实践。将关于CSS的选择器、继承、层叠、格式化、边距、填充、边框、图片、网站导航、表格、表单、浮动布局、定位网页上的元素,以及用于打印网页的CSS等技术通过逐步地讲解与教程串联了起来。每章内容从简单到复杂,一步一步地建立起一个完整的教程示例,并在每章都会详细讨论一些技巧、最佳实践和各浏览器之间一致性的兼容问题及如......一起来看看 《CSS实战手册(第2版)》 这本书的介绍吧!