同步之条件变量sync.Cond sync.Cond 的结构体
type Cond struct { // L is held while observing or changing the condition L Locker sema syncSema waiters uint32 // number of waiters checker copyChecker}
sync.Cond 的方法
//阻塞当前的goroutine//方法Wait会自动的对与该条件变量关联的那个锁进行解锁,并且使调用方所在的Goroutine被阻塞。//一旦该方法收到通知,就会试图再次锁定该锁。//如果锁定成功,它就会唤醒那个被它阻塞的Goroutine。//否则,该方法会等待下一个通知,那个Goroutine也会继续被阻塞。func (c *Cond) Wait() { c.checker.check() if race.Enabled { race.Disable() } //原子递增等待者计数,然后获取信号量进入休眠 atomic.AddUint32(&c.waiters, 1) if race.Enabled { race.Enable() } c.L.Unlock() runtime_Syncsemacquire(&c.sema) c.L.Lock()}func (c *Cond) Signal() {}func (c *Cond) Broadcast() {}
先看一个简单的用法,这样一个场景: 在控制台输入enter,作为一个发送通知的信号,使阻塞的goroutine继续执行。
package mainimport ( "os" "fmt" "sync" "time")func main() { locker := sync.Mutex{} cond := sync.NewCond(&locker) go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte cond.Signal()//当键盘输入enter后,发出通知信号 fmt.Println("signal...") }() go func() { cond.L.Lock() //首先进行锁定,与之关联的条件变量的锁定 fmt.Println("wait before...") //等待Cond消息通知 cond.Wait() fmt.Println("wait end...") cond.L.Unlock() }() time.Sleep(10 * time.Second) fmt.Println("exit...")}
运行结果,
wait before...signal...wait end...exit...
在打印出wait before...后,然后在控制台输入空格,最后wait end。 问题来了,如果在wait 之前输入了enter怎么办,也就是说提前发出了信号怎么办?如下代码,
package mainimport ( "os" "fmt" "sync" "time")func main() { var wg sync.WaitGroup locker := sync.Mutex{} cond := sync.NewCond(&locker) go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte cond.Signal()//当键盘输入enter后,发出通知信号 fmt.Println("signal...") }() time.Sleep(5 * time.Second) fmt.Println("sleep end...") wg.Add(1) go func() { defer wg.Done() cond.L.Lock() //首先进行锁定,与之关联的条件变量的锁定 fmt.Println("wait before...") //等待Cond消息通知 cond.Wait() fmt.Println("wait end...") cond.L.Unlock() }() wg.Wait() fmt.Println("exit...")}
运行结果,
signal...sleep end...wait before...fatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:sync.runtime_Semacquire(0xc82000a28c) /usr/local/go/src/runtime/sema.go:47 +0x26sync.(*WaitGroup).Wait(0xc82000a280) /usr/local/go/src/sync/waitgroup.go:127 +0xb4main.main() /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:36 +0x245goroutine 7 [semacquire]:sync.runtime_Syncsemacquire(0xc820010290) /usr/local/go/src/runtime/sema.go:241 +0x201sync.(*Cond).Wait(0xc820010280) /usr/local/go/src/sync/cond.go:63 +0x9bmain.main.func2(0xc82000a280, 0xc820010280) /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:31 +0x159created by main.main /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:34 +0x237exit status 2Process finished with exit code 1
在sleep end...之前输入enter,这时会发出信号,当sleep 结束之后,当执行接下来的goroutine时就会发生错误,即使wait了,也不会发出信号了。怎么才能避免这种情况呢?推荐用法如下,
package mainimport ( "fmt" "os" "sync" "time")func main() { var wg sync.WaitGroup locker := sync.Mutex{} cond := sync.NewCond(&locker) var condition bool = false go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte cond.L.Lock() cond.Signal() //当键盘输入enter后,发出通知信号 condition = true //把条件变量设为true,表示发送过信号 fmt.Println("signal...") cond.L.Unlock() }() time.Sleep(5 * time.Second) fmt.Println("sleep end...") wg.Add(1) go func() { defer wg.Done() cond.L.Lock() //首先进行锁定,与之关联的条件变量的锁定 fmt.Println("wait before...") //等待Cond消息通知 for !condition { //当条件为真时,不会发生wait fmt.Println("wait...") cond.Wait() } fmt.Println("wait end...") cond.L.Unlock() }() wg.Wait() fmt.Println("exit...")}
在sleep 之前输入enter,
signal...sleep end...wait before...wait end...exit...
在sleep之后输入enter,
sleep end...wait before...wait...signal...wait end...exit...
=======END=======