同步之條件變量sync.Cond sync.Cond 的結構體ui
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 的方法atom
//阻塞當前的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繼續執行。code
package main import ( "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怎麼辦,也就是說提早發出了信號怎麼辦?以下代碼,it
package main import ( "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...") }
運行結果,io
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 +0x26 sync.(*WaitGroup).Wait(0xc82000a280) /usr/local/go/src/sync/waitgroup.go:127 +0xb4 main.main() /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:36 +0x245 goroutine 7 [semacquire]: sync.runtime_Syncsemacquire(0xc820010290) /usr/local/go/src/runtime/sema.go:241 +0x201 sync.(*Cond).Wait(0xc820010280) /usr/local/go/src/sync/cond.go:63 +0x9b main.main.func2(0xc82000a280, 0xc820010280) /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:31 +0x159 created by main.main /Users/xinxingegeya/gogogo/ucar.com/cond/hello.go:34 +0x237 exit status 2 Process finished with exit code 1
在sleep end...以前輸入enter,這時會發出信號,當sleep 結束以後,當執行接下來的goroutine時就會發生錯誤,即便wait了,也不會發出信號了。怎麼才能避免這種狀況呢?推薦用法以下,ast
package main import ( "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,import
signal... sleep end... wait before... wait end... exit...
在sleep以後輸入enter,變量
sleep end... wait before... wait... signal... wait end... exit...
=======END=======方法