1.条件变量
条件变量:条件变量的作用并不保证在同一时刻仅有一个协程(线程)访问某个共享的数据资源,而是在对应的共享数据的状态发生变化时,通知阻塞在某个条件上的协程(线程)。条件变量不是锁,在并发中不能达到同步的目的,因此条件变量总是与锁一块使用。
GO标准库中的sync.Cond类型代表了条件变量。条件变量要与锁(互斥锁,或者读写锁)一起使用。成员变量L代表与条件变量搭配使用的锁。
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
checker copyChecke
}
对应的有3个常用方法,Wait,Signal,Broadcast。
1)func (c *Cond) Wait()
该函数的作用可归纳为如下三点:
a)阻塞等待条件变量满足
b)释放已掌握的互斥锁相当于cond.L.Unlock()。 注意:两步为一个原子操作。
c)当被唤醒,Wait()函数返回时,解除阻塞并重新获取互斥锁。相当于cond.L.Lock()
2)func (c *Cond) Signal()
单发通知,给一个正等待(阻塞)在该条件变量上的goroutine(线程)发送通知。
3)func (c *Cond) Broadcast()
广播通知,给正在等待(阻塞)在该条件变量上的所有goroutine(线程)发送通知。
下面我们用条件变量来编写一个优化“生产者消费者模型”
示例代码:
package main
import (
"sync"
"math/rand"
"time"
"fmt"
)
//全局变量条件变量
var cond sync.Cond
func producer(ch chan<- int ,idx int) {
//循环生产
for{
//条件变量互斥加锁
cond.L.Lock()
//当管道满后阻塞等待消费者处理
for len(ch)==3{
cond.Wait()
}
//随机生产数据并写入管道
num:=rand.Intn(500)
ch<-num
fmt.Printf("%d生产者正在生产数据%d,缓冲区还剩数据位置%d\n",idx,num,len(ch))
//条件变量互斥解锁
cond.L.Unlock()
//唤醒对端
cond.Signal()
}
}
func consumer(ch <-chan int ,idx int) {
//循环消费
for {
//条件变量互斥加锁
cond.L.Lock()
//当管道没有内容时阻塞等待生产者处生产
for len(ch)==0{
cond.Wait()
}
//从管道中读取数据
num:=<-ch
fmt.Printf("%d消费者正在消费数据%d,缓冲区还剩数据位置%d\n",idx,num,len(ch))
//条件变量互斥解锁
cond.L.Unlock()
//唤醒对端
cond.Signal()
}
}
func main() {
//创建随机数种子
rand.Seed(time.Now().UnixNano())
//创建通信管道
ch := make(chan int,3)
quit:=make(chan bool)
//创建互斥锁和条件变量
cond.L = new(sync.Mutex)
//3个生产者
for i := 0; i < 3; i++ {
go producer(ch,i+1)
}
//3个消费者
for i := 0; i < 3; i++ {
go consumer(ch,i+1)
}
//阻塞程序,不结束主进程
<-quit
}
//输出结果
//1生产者正在生产数据217,缓冲区还剩数据位置1
//1生产者正在生产数据4,缓冲区还剩数据位置2
//1生产者正在生产数据311,缓冲区还剩数据位置3
//3消费者正在消费数据217,缓冲区还剩数据位置2
//3消费者正在消费数据4,缓冲区还剩数据位置1
//3消费者正在消费数据311,缓冲区还剩数据位置0
//1生产者正在生产数据444,缓冲区还剩数据位置1
//1生产者正在生产数据339,缓冲区还剩数据位置2
//2消费者正在消费数据444,缓冲区还剩数据位置1
//2消费者正在消费数据339,缓冲区还剩数据位置0
//2生产者正在生产数据371,缓冲区还剩数据位置1
//2生产者正在生产数据162,缓冲区还剩数据位置2
//2生产者正在生产数据386,缓冲区还剩数据位置3
//2消费者正在消费数据371,缓冲区还剩数据位置2
//2消费者正在消费数据162,缓冲区还剩数据位置1
//2消费者正在消费数据386,缓冲区还剩数据位置0
1)main函数中定义quit,其作用是让主协程阻塞。
2)定义product作为队列,生产者产生数据保存至队列中,最多存储3个数据,消费者从中取出数据模拟消费
3)条件变量要与锁一起使用,这里定义全局条件变量cond,它有一个属性:L Locker。是一个互斥锁。
4)开启3个消费者协程,开启3个生产者协程。
5)producer生产者,在该方法中开启互斥锁,保证数据完整性。并且判断队列是否满,如果已满,调用wait()让该goroutine阻塞。当消费者取出数后执行cond.Signal(),会唤醒该goroutine,继续生产数据。
6)consumer消费者,同样开启互斥锁,保证数据完整性。判断队列是否为空,如果为空,调用wait()使得当前goroutine阻塞。当生产者产生数据并添加到队列,执行cond.Signal() 唤醒该goroutine。