【Go】cond条件变量

【Go】cond条件变量

在了解cond之前,可以先了解一下生产者消费者模式

比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相当于消费者

经典并发同步模式:生产者-消费者设计模式 - 知乎 (zhihu.com)

那么在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方法则没有上解锁操作,只有通知作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值