条件变量的作用并不保证同一时刻仅有一个协程(线程)访问某个共享的数据资源,而是在对应的共享数据的状态发生变化时,通知阻塞在某个条件上的协程(线程)。条件变量不是锁,在兵法中不能达到同步的目的,因此条件变量总是与锁一起使用。
Go标准库中的sync.Cond类型代表了条件变量。条件鼻梁要与锁(互斥锁或者读写锁)一起使用,成员变量L代表与条件变量代培使用的锁。
type Cond struct { noCopy noCopy // L is held while observing or changing the condition L Locker notify notifyList checker copyChecker }
对应的有3个方法:Wait, Signal, Broadcast
- func (c *Cond) Wait()
该函数的作用可归纳为如下三点:- 阻塞等待条件变量满足
- 释放已掌握的互斥锁,相当于cond.L.Unlock(). 注意:两步为一个原子操作。
- 当被唤醒,Wait()函数返回时,解除阻塞并重新获取互斥锁。相当于cond.L.Lock()
- func (c *Cond) Signal()
单发通知,给一个正等待(阻塞)在该条件变量上的goroutine(线程) 发送通知 - func (c *Cond) Broadcast()
广播通知,给正在等待(阻塞)在该条件变量上的所有goroutine(线程)发送通知
使用实例:
var cond sync.Cond //生产者 func producer(out chan<- int, index int) { for { cond.L.Lock() for len(out) == 10 { fmt.Println(index, "len == 10") cond.Wait() //阻塞 等待 } num := rand.Intn(800) time.Sleep(time.Second) out <- num fmt.Println("生产者:", index, num) cond.L.Unlock() cond.Signal() //唤醒阻塞的协程 } } //消费者 func consumer(in <-chan int, index int) { for { cond.L.Lock() for len(in) == 0 { fmt.Println(index, "len == 0") cond.Wait() //阻塞 等待 } time.Sleep(time.Second) num := <-in fmt.Println("消费者:", index, num) cond.L.Unlock() cond.Signal() //唤醒阻塞的协程 } } func main() { ch := make(chan int, 10) rand.Seed(time.Now().UnixMilli()) cond.L = new(sync.Mutex) for i := 1; i <= 4; i++ { go producer(ch, i) } for i := 1; i <= 6; i++ { go consumer(ch, i) } quit := make(chan []struct{}) <-quit }