Go
语言sync package提供了条件变量(condition variable
)类型:
type Cond struct {
// L is held while observing or changing the condition
L Locker
// contains filtered or unexported fields
}
type Cond
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast()
func (c *Cond) Signal()
func (c *Cond) Wait()
type Locker
type Locker interface {
Lock()
Unlock()
}
A Locker represents an object that can be locked and unlocked.
NewCond()
函数输入参数是一个Locker
接口类型,即实现了锁功能的变量。Broadcast()
函数通知所有等待在condition variable
的goroutine
,而Signal()
函数只会通知其中的一个goroutine
。Wait()
会让goroutine
阻塞在condition variable
,等待条件成立。通常的做法是:
c.L.Lock()
for !condition() {
c.Wait()
}
... make use of condition ...
c.L.Unlock()
进入Wait()
函数会解锁,离开Wait()
函数会重新加锁。由于在“解锁->收到通知->重新加锁”这段逻辑中间有可能另一个同样wait()
的goroutine
抢先一步改变了条件,导致当前goroutine
的条件不再成立,所以这块要使用循环检测。参考下例:
package main
import (
"sync"
"time"
"fmt"
)
func main() {
c := sync.NewCond(&sync.Mutex{})
var num int
for i := 1; i <= 2; i++ {
go func(id int) {
fmt.Println("Enter Thread ID:", id)
c.L.Lock()
for num != 1 {
fmt.Println("Enter loop: Thread ID:", id)
c.Wait()
fmt.Println("Exit loop: Thread ID:", id)
}
num++
c.L.Unlock()
fmt.Println("Exit Thread ID:", id)
}(i)
}
time.Sleep(time.Second)
fmt.Println("Sleep 1 second")
num++
c.Broadcast()
time.Sleep(time.Second)
fmt.Println("Program exit")
}
一次执行结果如下:
Enter Thread ID: 2
Enter loop: Thread ID: 2
Enter Thread ID: 1
Enter loop: Thread ID: 1
Sleep 1 second
Exit loop: Thread ID: 2
Exit Thread ID: 2
Exit loop: Thread ID: 1
Enter loop: Thread ID: 1
Program exit
从上面例子可以看出由于goroutine 2
改变了条件,导致goroutine 1
重新进入循环,即多个goroutine
阻塞在一个condition variable
上存在着竞争的关系。