诡异的bug,sync.signal 的接锅侠

1. 先来一段内含高能bug的golang代码

package main

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

var cond sync.Cond

func producer(in chan<- int) {
        for {
                cond.L.Lock()
                for len(in) >= 3 {
                        cond.Wait()
                }
                num := rand.Intn(100)
                fmt.Println("生产:", num)
                in <- num
                cond.Signal()
                cond.L.Unlock()
        }
}

func customer(out <-chan int) {
        for {
                cond.L.Lock()
                for len(out) == 0 {
                        cond.Wait()
                }
                num := <-out
                fmt.Println("消费:", num)
                cond.Signal()
                cond.L.Unlock()
        }
}

func main() {

        rand.Seed(time.Now().UnixNano())
        c := make(chan int, 50)
        cond.L = new(sync.Mutex)

        for i := 1; i <= 5; i++ {
                go producer(c)
        }

        for i := 1; i <= 5; i++ {
                go customer(c)
        }

        select {}
} 

 

2. 这段代码实现了生产者和消费者模型,但是分分钟死锁。。。。

 

3. 死锁产生场景分析:
考虑初始状态,当前通道内有0个数据,所有customer先执行,然后全部阻塞在wait(处于asleep状态),然后跑在5个cpu线程上的5个producer3个正在生产数据,另外两个在阻塞(处于asleep状态),当3个producer生产完数据后,每次调用signal函数却都唤醒了1个处于asleep状态的producer,处于asleep状态的producer被唤醒后发现条件仍然不满足,继续阻塞在wait函数(处于asleep状态),这时,生产完3个数据后,先前的3个producer也进入了asleep状态,而主线程仍然阻塞在select语句,至此,所有goroutine都被阻塞了,死锁产生了

 

4. 产生原因分析:
1. signal函数只是无脑的唤醒一个goroutine,它并不会区分是customer还是producer
2.在多核场景下,会产生上述的场景
3. 在单核场景下(尤其是在生产者远远多于消费者的场景时,比如5:50),由于signal是异步的,会导致类似上述场景

 

5. 程序bug修复:
方案1. 把signal函数换成broadcast函数,但是这样会带来惊群,导致对cpu的竞争,降低效率,于cond设计的初衷不服
方案2. 在wait函数前边加上signal函数,这样就可以在唤醒错误的goroutine的时候在重新发送一个“补偿”信号,但是这样也会导致不必要的cpu竞争,但是没有方案1那么激烈

6. 总结:

归根结底,1. signal函数唤醒的goroutine具有不确定性;2. signal函数是异步的

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值