条件变量的初始化离不开互斥锁,它的方法有的也是基于互斥锁的。
条件变量可以协调想要访问共享资源的线程。
当共享资源的状态发生变化时,它可以被用来通知被互斥锁阻塞的线程。
条件变量的 Wait 方法做了什么?
- 把调用它的 goroutine 加入到当前条件变量的通知队列中。
- 解锁当前的条件变量基于的那个互斥锁。所以在调用该 Wait 方法前,必须先锁定互斥锁,否则会引发不可恢复的 panic。
- 让当前 goroutine 处于等待状态,等到通知到来时再决定是否唤醒它。
此时,该 goroutine 就会阻塞在调用该 Wait 方法的那行代码上。 - 如果通知到来并决定唤醒该 goroutine,则在唤醒它后重写锁定当前条件变量基于的互斥锁。
自此后,当前的 goroutine 就会继续执行后面的代码了。
使用 for 检测资源状态的原因:
如果一个 goroutine 因收到通知而被唤醒,但却发现共享资源的状态依然不符合它的要求,那么就应该再次调用条件变量的 Wait 方法,并继续等待下次通知的到来。
package main
import (
"log"
"sync"
"time"
)
func main() {
var mailbox uint8 // 信箱,0 表示空,1 表示满
var lock sync.RWMutex // 信箱上的锁
sendCond := sync.NewCond(&lock) // 参数为 lock 的指针值
recvCond := sync.NewCond(lock.RLocker())
sign := make(chan struct{}, 3)
max := 3
// 发信
go func(max int) {
defer func() {
sign <- struct{}{}
}()
for i := 0; 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 mailbox has been sent.", i)
lock.Unlock() // 解锁信箱
recvCond.Signal() // 通知收信者
}
}(max)
// 收信
go func(max int) {
defer func() {
sign <- struct{}{}
}()
for j := 0; 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
}
条件变量的 Signal 方法和 Broadcase 方法的异同:
两种都是用来发送通知的,但前者只唤醒一个等待的 goroutine,而后者唤醒所有等待的 goroutine。
package main
import (
"log"
"sync"
"time"
)
func main() {
var mailbox uint8 // 信箱,0 表示空,1 表示满
var lock sync.RWMutex // 信箱上的锁
sendCond := sync.NewCond(&lock) // lock 的指针值为参数
recvCond := sync.NewCond(&lock)
// 发信
send := func(id, index int) {
lock.Lock() // 锁住信箱
for mailbox == 1 { // 信箱满
sendCond.Wait() // 等待信箱空
}
log.Printf("sender [%d-%d]: the mailbox is empty.", id, index)
mailbox = 1 // 发信,信箱满
log.Printf("sender [%d-%d]: the mailbox has been sent.", id, index)
lock.Unlock() // 解锁信箱
recvCond.Broadcast() // 唤醒所有收信者
}
// 收信
recv := func(id, index int) {
lock.Lock()
for mailbox == 0 {
recvCond.Wait()
}
log.Printf("receiver [%d-%d]: the mailbox is full.", id, index)
mailbox = 0
log.Printf("receiver [%d-%d]: the letter has been received.", id, index)
lock.Unlock()
sendCond.Signal() // 只有一个发信者需要唤醒
}
sign := make(chan struct{}, 3)
max := 4
// 发信
go func(id, max int) {
defer func() {
sign <- struct{}{}
}()
for i := 0; i < max; i++ {
time.Sleep(time.Millisecond * 500)
send(id, i)
}
}(0, max)
// 收信
go func(id, max int) {
defer func() {
sign <- struct{}{}
}()
for i := 0; i < max; i++ {
time.Sleep(time.Millisecond * 200)
recv(id, i)
}
}(1, max/2)
// 收信
go func(id, max int) {
defer func() {
sign <- struct{}{}
}()
for i := 0; i < max; i++ {
time.Sleep(time.Millisecond * 200)
recv(id, i)
}
}(2, max/2)
<- sign
<- sign
<- sign
}