【Go】cond条件变量
在了解cond之前,可以先了解一下生产者消费者模式
比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相当于消费者
那么在go语言,要怎么去实现呢?
第一个想法,使用两个go协程分别for循环生产和消费即可,但是可能会产生并发问题,因此要上锁。
//略package import
type Product struct{
len int
cap int
lock sync.RWMutex
}
func main() {
data := Product{
len: 0,//当前库存0
cap: 10,//容量10
lock: sync.RWMutex{},//读写锁
}
go func() {
//0.1秒生产1次
for{
data.lock.Lock()
if data.len==data.cap {data.lock.Unlock();continue}//若库满则停止生产等待消费(继续循环判断)
data.len++
log.Printf("生产至%d件",data.len)
data.lock.Unlock()
time.Sleep(100 * time.Millisecond)//模拟生产商品时间
}
}()
go func() {
//0.5秒消费1次
for{
data.lock.Lock()
if data.len == 0{data.lock.Unlock();continue}//若无库存则停止消费等待生产(继续循环判断)
data.len--
log.Printf("消费至%d件",data.len)
data.lock.Unlock()
time.Sleep(500 * time.Millisecond)//模拟消费商品时间
}
}()
time.Sleep(3*time.Second)
}
-
缺点:判断是否生产商品需要一直循环,浪费资源
解决办法1:判断商品已经库满时,休息固定时间
缺点:在休息时间内不能及时生产
解决办法2:当商品已经库满时休息,库不满时开始生产
第一种解决办法直接在协程的判断中添加休息时间即可
//略上下文 if data.len==data.cap { data.lock.Unlock() time.Sleep(100 * time.Millisecond)//添加休息时间 continue }
优点是比原先减少了空转时间,缺点比较明显,在休息时间内不能生产
第二种解决办法,使用go的条件变量cond
cond可以通知唤醒其他协程,减少资源消耗,也能及时进行生产
首先是创建条件变量,cond需要和锁配合使用。创建方法如下:
type Product struct{ len int cap int lock sync.RWMutex cond *sync.Cond//条件变量 } func main(){ data := Product{len: 0,cap: 10,lock: sync.RWMutex{},} data.cond=sync.NewCond(&data.lock)//创建条件变量,匹配对应锁 //…… }
创建好条件变量后,明确一下:
①我们需要在库存为0时,消费者等待生产
②我们需要在库存已满时,生产者等待消费
在这两个判断中添加cond.Wait()函数,表示进入休息,等待被唤醒,在生产或消费结束后,添加cond.Signal()(表示唤醒一个阻塞的其他协程):
func main(){ //…… go func() { //0.1秒生产1次 for{ data.lock.Lock() if data.len==data.cap { data.cond.Wait()//wait()被signal()唤醒之后将继续向下执行 } data.len++ log.Printf("生产至%d件",data.len) data.lock.Unlock() //生产结束 if data.len > 0{ data.cond.Signal() } //模拟生产商品时间 time.Sleep(100 * time.Millisecond) } }() go func() { //0.5秒消费1次 for{ data.lock.Lock() if data.len == 0{ data.cond.Wait() } data.len-- log.Printf("消费至%d件",data.len) data.lock.Unlock() //消费结束 data.cond.Signal() //模拟消费商品时间 time.Sleep(500 * time.Millisecond) } }() //…… }
这样解决方案二就完成了。完整代码如下:
package main import ( "log" "sync" "time" ) type Product struct{ len int cap int lock sync.RWMutex cond *sync.Cond } func main() { data := Product{ len: 0,//当前库存0 cap: 10,//容量10 lock: sync.RWMutex{}, } data.cond=sync.NewCond(&data.lock) go func() { // 问题:判断是否生产商品需要一直循环,浪费资源 // 解决办法1:判断商品已经库满时,休息固定时间 // 解决办法2:当商品已经库满时休息,库不满时开始生产 //0.1秒生产1次 for{ data.lock.Lock() if data.len==data.cap { data.cond.Wait() } data.len++ log.Printf("生产至%d件",data.len) data.lock.Unlock() data.cond.Signal() //模拟生产商品时间 time.Sleep(100 * time.Millisecond) } }() go func() { //0.5秒消费1次 for{ data.lock.Lock() if data.len == 0{ data.cond.Wait() } data.len-- log.Printf("消费至%d件",data.len) data.lock.Unlock() data.cond.Signal() //模拟消费商品时间 time.Sleep(500 * time.Millisecond) } }() time.Sleep(5*time.Second) }
下面就来简单理解一下wait方法:
wait方法的流程基本如下- 解锁(将锁让出,避免其他协程无法获得锁)
- 等待通知
- 被通知后上锁,继续执行
至于signal和broadcast方法则没有上解锁操作,只有通知作用。