Go语言同步编程:不要让吃货们做无谓的竞争(go语言编程实战)

网友投稿 288 2022-06-17


我们在此前曾经介绍过Go语言当中的mutex(锁)机制,很多时候多个Goroutine之间的同步问题靠锁就可以解决了。但是有些时候,光有锁不能很好的解决问题。比如下面的问题:

当多个吃货们都想去争抢面包时,为了避免出现共同争抢面包(共享资源)的问题,我们可以使用一把锁才确保彼此之间有先有后的去操作。然而有一种可能就是此时面包的容器内(面包店)并未有面包,因此这些吃货们即使抢到锁成功了,也不得不面对吃不到面包的尴尬境地。

站在编程人员的角度,如果我们抢到锁,吃完面包就应该释放,如果没有面包怎么办?本着占着茅坑不怎么怎么样的原则,我们其实应该把锁释放。那么接下来会发生什么呢?多个吃货们继续去争抢锁,争抢到了之后发现还是没有面包,于是无限循环下去。在这个情况下,我们发现吃货们经常要做一些无谓的竞争,但是却没有实际的效果!

其实大家也看到了,上面的问题就是大名鼎鼎的生产者与消费者问题。解决生产者和消费者的关键就是避免不必要的竞争,此时光靠互斥锁是不行的,我们需要借助条件变量。

type Cond struct {

// 在观测或更改条件时L会冻结 L Locker // 包含隐藏或非导出字段 }

//Locker是一个interface type Locker interface { Lock() Unlock()

}

通过上述结构,我们可以看出来,条件变量内部会包含一个Locker的对象,这个Locker是一个interface,只要支持Lock与Unlock两个方法,就有资格成为Locker的一个对象。也就是说我们要使用条件变量,必须要先有一个mutex类似的锁。下面再来看看Cond的方法有哪些:

func NewCond(l Locker) *Cond func (c *Cond) Broadcast() func (c *Cond) Signal() func (c *Cond) Wait()

方法说明:

NewCond 传入一把锁,构造一个条件变量

Wait 阻塞等待条件发生

Broadcast/Signal 唤醒多个/一个阻塞在该条件上的Goroutine

其中Wait函数还要再强调一下:

Wait执行前,该Goroutine必须已经拥有了锁

Wait执行时,会释放锁,并阻塞等待条件触发

当条件被触发时,阻塞在Wait的Goroutine被唤醒,并再次去抢锁

仔细以上介绍,我们仔细分析一下,多个吃货们来抢面包时先抢锁,假设吃货A先抢到了锁,那么他去看面包没有,就去Wait阻塞等待,此时这个锁又被释放了,那么其他吃货就又可以抢到锁了,同理吃货B抢到后面临和A一样的操作,那么吃货B最终也是阻塞等待,并且这把锁又被释放了,这样就很好的解决了大家无谓竞争的问题,当没有面包的情况下,大家都安静的等待条件的发生。当事情真的发生了,那就再继续抢锁就可以了。

下面我们使用Go语言来实现一个简单的生产者和消费者模型,我们定义一个面包筐:

var five [5]int var five_count int

面包筐最大上限是5,这样面包生产者也不是肆无忌惮的生产,为此我们需要定义2组条件变量,一组用来控制消费者们,一组用来控制生产者的肆意生产。

var cond *sync.Cond var mutex sync.Mutex //每个条件变量都要绑定一个锁 var cond_five *sync.Cond var mutex_five sync.Mutex

我们来实现一下生产者部分,five模拟一个环形队列:

func productor() { index := 0 prodnum := 1000 //生产的编号 for {

//判断是否可以生产 mutex.Lock()

time.Sleep(time.Millisecond * 200) //模拟生产消耗的时间 five[index] = prodnum //真正的生产 fmt.Println("productor prodnum========", prodnum)

prodnum++

five_count++

index = (index + 1) % 5 mutex.Unlock()

cond.Signal() // 通知消费者 if five_count == 5 {

//生产者需要休息一会 five_mutex.Lock()

five_cond.Wait()

//1. Wait 之前先要有锁,wait时释放锁,唤醒时去抢锁 five_mutex.Unlock()

}

} wg.Done()

}

我们再来实现消费者:

//消费者 func customer(num int) {

for {

mutex.Lock() //wait前要抢锁 if five_count > 0 {

time.Sleep(time.Millisecond * 10) fmt.Printf("I am %d customer, prodnum == %d\n", num, five[customer_index]) customer_index = (customer_index + 1) % 5 five_count--

five_cond.Signal() //通知生产者 }

cond.Wait() mutex.Unlock() }

wg.Done() }

完整代码如下:

//条件变量 /*

Wait:

1. 阻塞等待 Cond的发生(被唤醒) 2. 调用Wait之前,goroutine必须已经抢到锁 调用Wait时,释放锁,同时阻塞 当被唤醒时,再去抢锁,抢到后才能继续往下执行 */ package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup //消费者要等待的条件 var cond *sync.Cond var mutex sync.Mutex //生产者也有要等待的条件(可以继续生产,five_count < 5) var five_cond *sync.Cond var five_mutex sync.Mutex //共享资源 var five [5]int var five_count int = 0 var customer_index int = 0 func productor() {

index := 0 prodnum := 1000 //生产的编号 for { //判断是否可以生产 mutex.Lock()

time.Sleep(time.Millisecond * 200) //模拟生产消耗的时间 five[index] = prodnum //真正的生产 fmt.Println("productor prodnum========", prodnum)

prodnum++

five_count++

index = (index + 1) % 5 mutex.Unlock()

cond.Signal() // 通知消费者 if five_count == 5 { //生产者需要休息一会 five_mutex.Lock()

five_cond.Wait() //1. Wait 之前先要有锁,wait时释放锁,唤醒时去抢锁 five_mutex.Unlock()

}

}

wg.Done()

} //消费者 func customer(num int) { for {

mutex.Lock() //wait前要抢锁 if five_count > 0 {

time.Sleep(time.Millisecond * 10)

fmt.Printf("I am %d customer, prodnum == %d\n", num, five[customer_index])

customer_index = (customer_index + 1) % 5 five_count--

five_cond.Signal() //通知生产者 }

cond.Wait()

mutex.Unlock()

}

wg.Done()

} func main() { //条件变量初始化 cond = sync.NewCond(&mutex)

five_cond = sync.NewCond(&five_mutex)

wg.Add(3) go productor() go customer(1) go customer(2)

wg.Wait()

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:大神是如何学习 Go 语言之浅谈 select 的实现原理
下一篇:go语言 web框架比较(go语言web开发)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~