跳到主要内容

同步3-条件变量

一 条件变量

sync.Cond类型即是Go中的条件变量,该类型内部包含一个锁接口。条件变量通常与锁配合使用:

创建条件变量的函数:

func NewCond(l locker) *Cond        // 条件变量必须传入一个锁,二者需要配合使用

*sync.Cond类型有三个方法:

  • Wait: 该方法会阻塞等待条件变量满足条件。也会对锁进行解锁,一旦收到通知则唤醒,并立即锁定该锁
  • Signal: 发送通知(单发),给一个正在等待在该条件变量上的协程发送通知
  • Broadcast: 发送通知(广播),给正在等待该条件变量的所有协程发送通知,容易产生 惊群

示例:

func main() {

cond := sync.NewCond(&sync.Mutex{})

condition := false

// 开启一个新的协程,修改变量 condition
go func() {

time.Sleep(time.Second * 1)
cond.L.Lock()

condition = true // 状态变更,发送通知
cond.Signal() // 发信号

cond.L.Unlock()
}()

// main协程 是被通知的对象,等待通知
cond.L.Lock()
for !condition {
cond.Wait() // 内部释放了锁(释放后,子协程能拿到锁),并等待通知(消息)
fmt.Println("获取到了消息")
}
cond.L.Unlock() // 接到通知后,会被再次锁住,所以需要在需要的场合释放

fmt.Println("运行结束")
}

使用条件变量优化生产消费模型(支持多个生产者、多个消费者):

package main

import (
"fmt"
"math/rand"
"sync"
"time"
)

// 定义缓冲区大小
const BUFLEN = 5

// 全局位置定义全局变量
var cond *sync.Cond = sync.NewCond(&sync.Mutex{})

// 生产者
func producer(ch chan<- int) {
for {
cond.L.Lock() // 给条件变量对应的互斥锁加锁
for len(ch) == BUFLEN { // 缓冲区满,则等待消费者消费,这里不能是if
cond.Wait()
}
ch <- rand.Intn(1000) // 写入缓冲区一个随机数
cond.L.Unlock() // 生产结束,解锁互斥锁
cond.Signal() // 一旦生产后,就唤醒其他被阻塞的消费者
time.Sleep(time.Second * 2)
}
}

// 消费者
func consumer(ch <-chan int) {
for {
cond.L.Lock() // 全局条件变量加锁
for len(ch) == 0 { // 如果缓冲区为空,则等待生产者生产,,这里不能是if
cond.Wait() // 挂起当前协程,等待条件变量满足,唤醒生产者
}
fmt.Println("Receive:", <-ch)
cond.L.Unlock()
cond.Signal()
time.Sleep(time.Second * 1)
}
}

func main() {

rand.Seed(time.Now().UnixNano()) // 设置随机数种子

// 生产消费模型中的
ch := make(chan int, BUFLEN)

// 启动10个生产者
for i := 0; i < 10; i++ {
go producer(ch)
}

// 启动10个消费者
for i := 0; i < 10; i++ {
go consumer(ch)
}

// 阻塞主程序退出
for {

}
}