什么是条件变量
- 条件变量并不是用来保护临界区和共享资源,它是用来协调想要访问共享资源的线程
- 条件变量提供三个方法:等待通知(wait)、单发通知(signal)、广发通知(broadcast)
条件变量 VS 互斥锁
- 条件变量基于互斥锁,条件变量必须有互斥锁的支撑才能起作用
- 在条件变量等待通知的时候需要互斥锁的锁定才能起作用
- 在条件变量进行单发通知或者广发通知的时候需要互斥锁的解锁才能起作用
如何使用条件变量
- sendCond和recvCond都是*sync.Cond类型的,同时也都是由sync.NewCond函数来初始化的
- sync.NewCond 函数需要lock变量的指针作为参数
DEMO:
package main
import (
"log"
"sync"
"time"
)
func main() {
// mailbox 代表信箱
// 0代表是空的,1代表信箱是满的
var mailbox uint8
// lock 代表信箱上的锁
var lock sync.RWMutex
// sendCond 代表发信的条件变量
sendCond := sync.NewCond(&lock) // *sync.Cond 类型,参数需要lock变量的指针
// recvCond 代表收信的条件变量
recvCond := sync.NewCond(lock.RLocker()) // *sync.Cond 类型,参数需要lock变量的指针
// sign 用于传递演示完成的信号
sign := make(chan struct{}, 3)
max := 5
go func(max int) { // 用于发信
defer func(){
sign <- struct{}{}
}()
for i := 1; i <= max; i++ {
time.Sleep(time.Millisecond * 500)
lock.Lock()
for mailbox == 1 {
sendCond.Wait()
}
log.Printf("sender [%d]: the mailbox is empty.", i)
mailbox = 1
log.Printf("sender [%d]: the letter has been sent.", i)
lock.Unlock()
recvCond.Signal()
}
}(max)
go func(max int) { // 用于收信
defer func() {
sign <- struct{}{}
}()
for j := 1; j <= max; j ++ {
time.Sleep(time.Millisecond * 500)
lock.RLock()
for mailbox == 0 {
recvCond.Wait()
}
log.Printf("receiver [%d]: the mailbox is full.", j)
mailbox = 0
log.Printf("receiver [%d]: the letter has been received.", j)
lock.RUnlock()
sendCond.Signal()
}
}(max)
<-sign
<-sign
}
- sendCond 通过接收 sync.RWMutex(Lock方法和Unlock方法分别用于对写锁进行锁定和解锁) 指针类型的变量,实现条件变量
- recvCond 通过接收 sync.RWMutex.RLocker() 实现条件变量
- sync.RWMutex 类型中的lock方法和Unlock方法分别用于对写锁进行锁定和解锁,而它的RLock方法和RUnlock方法则分别用于对读锁进行锁定和解锁
recvCond 需要通过 sync.NewCond()接收可读sync.Locker类型 如:lock.RLocker()
条件变量的Wait方法
条件变量的Wait方法做了什么
- 把调用当前变量的goroutine添加到通知队列中
- 解锁当前变量的互斥锁
- 让当前的goroutine处于等待状态,等到通知到来时再决定是否唤醒它(此时的goroutine会一直阻塞在wait方法上)
- 如果通知到来后并决定唤醒当前这个goroutine,那么就在唤醒它之后重新锁定当前条件变量基于的互斥锁
为什么先要锁定当前条件变量的互斥锁,才能调用Wait方法
见上面
为什么用for语句来包裹调用Wait方法的表达式,而不是用if
如果一个 goroutine 因收到通知而被唤醒,但却发现共享资源的状态,依然不符合它的要求,那么就应该再次调用条件变量的Wait方法,并继续等待下次通知的到来
条件变量的Signal方法和Broadcast方法有哪些异同
- Signal方法和Broadcast方法都可用来发送通知
- Signal只会唤醒一个被用来等待的goroutine
- Broadcast则会唤醒所有等待的goroutine
- 这两个方法并不需要互斥锁的保护,因此最好不要在解锁互斥锁之前调用它们