跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 关于 Sync.Mutex 的竞争问题
未分類
12 9 月 2020

关于 Sync.Mutex 的竞争问题

关于 Sync.Mutex 的竞争问题

資深大佬 : Chaox 2

var mu sync.Mutex  func produce(ch chan<- int){  for i:=0;i<10;i++{   mu.Lock()   ch<-i   fmt.Println("produce:"+strconv.Itoa(i))   mu.Unlock()  } }  func consumer(ch <- chan int){  for i:=0;i<10;i++{   mu.Lock()   v:=<-ch   fmt.Println("consumer:"+strconv.Itoa(v))   mu.Unlock()  } } func main(){  ch:=make(chan int,5)   go produce(ch)  go consumer(ch)  time.Sleep(10*time.Second) } 

我是 go 的初学者,我今天写了一个问题代码。本意是让资源生成和消费的时候同时打印出该资源信息。 我知道当 consumer 先执行时会导致死锁,但是我不明白的是为什么 produce 先执行时,即使他释放了锁,consumer 也竞争不到锁,produce 会一直占有锁,这里面的竞争规则是什么样的?我在网上没有找到满意的答案

大佬有話說 (15)

  • 資深大佬 : silenzio

    你可以简单理解为没有规则 完全随机
    实际上 如果你要实现的是打印功能 完全不需要锁
    这两个 go 程没有修改共享变量 为什么要加锁呢? chan 就可以完成阻塞动作

  • 主 資深大佬 : Chaox

    @silenzio 谢谢解答。我写锁的目的是为了获得和消耗资源的同时,能够同时打印该资源。我最开始写的是不加锁的,但是我发现有时候他生成资源后,还没来得及打印,消费者已经消费并打印了。打印结果看起来不是很合逻辑。

  • 資深大佬 : zhs227

    你已经是走 chan 了,就不用锁了。另外,打印的顺序和多线程中实际执行的时间并不一定强相关
    你这样写,我脑补运行了一下,容易导致一边获取了锁以后,chan 又被另一方阻塞,程序完全执行不下去。

  • 資深大佬 : BingoXuan

    1. Print 并不保证线程安全,消费者生产者同时打印的结果可能会混起来
    2. 或者专门一个协程通过 chan 来获取需要打印的数据
    3. 用 sync.WaitGroup 同步不同协程,类似于 thread.join 的效果

  • 主 資深大佬 : Chaox

    @silenzio 而且好像获取规则也不是完全随机,因为我发现 produce 先执行,produce 能够一直抢到锁

  • 主 資深大佬 : Chaox

    @zhs227 是的,我完全理解你的解答,我的这个代码很有问题,但是我的疑惑是 consumer 永远抢不到 produce 释放的锁。我加锁的目的就是为了打印的顺序和多线程中实际执行的时间相关起来。

  • 資深大佬 : CEBBCAT

    我想问题可能出在对 ch 加锁那里,加入消费者消费过慢,那么有可能 produce 塞满了 ch 后进入下一次循环后,consumer 被锁阻塞了,所以死锁了

    关于打印顺序,就是这样的呀,向 ch 填充数据和 fmt 的打印不是原子的,之间可能存在协程切换

    我觉得你要么放弃纠结这个,要么换一个更好的办法解决日志问题

    go 的协程调度顺序我记不太清除了,但应该是两个 go 指令中的后一个会被先得到执行。但我想没有人会依赖这个

  • 資深大佬 : silenzio

    我的意思是你可以假设为安全随机 也就是说 你不能认为你贴出的代码 可以控制它的顺序 你不能假设 produce 永远先执行 你在写代码的时候 必须假设它是完全随机的

    复制你的代码用 code runner 执行 5 次 结果如下:
    有三次是这个情况: produce 执行 5 次 程序死锁
    有一次是这个情况: produce 执行 4 次 consumer 执行 1 次 produce 执行 2 次 程序死锁
    有一次是这个情况: consumer 先抢到锁 程序死锁

    这个结果受很多因素影响 比如
    go 程启动也是需要时间的. 你可以调整代码 先 go consumer(ch) 再 go produce(ch), 会发现 consumer 先抢到锁的几率大大提高 https://mp.weixin.qq.com/s/hIs318h6iJW2O9–QVqh6w

  • 資深大佬 : heimeil

    var mu sync.Mutex

    func produce(ch chan<- int) {
    for i := 0; i < 10; i++ {
    mu.Lock()
    ch <- i
    fmt.Println(“produce:” + strconv.Itoa(i))
    mu.Unlock()
    runtime.Gosched()
    }
    }

    func consumer(ch <-chan int) {
    for i := 0; i < 10; i++ {
    runtime.Gosched()
    mu.Lock()
    v := <-ch
    fmt.Println(“consumer:” + strconv.Itoa(v))
    mu.Unlock()
    }
    }

    func main() {
    ch := make(chan int, 5)
    go produce(ch)
    go consumer(ch)
    time.Sleep(10 * time.Second)
    }

    produce 的 for 循环不停加锁解锁,比其他协程的等待锁释放更容易竞争到锁,用 runtime.Gosched() 让出执行权给其他协程,让别的协程的锁也有拿到锁的可能,不过还是看运气,多跑几次

  • 主 資深大佬 : Chaox

    @silenzio 谢谢解答。我之前的运行状况是 produce 一直能抢到锁,我误以为其中有什么规则。看了你的解答后我加大了循环次数和管道容量,consumer 终于抢到 produce 释放的锁了。

  • 資深大佬 : zhs227

    因为你有 chan,如果没有这个 chan,就可以抢到锁了。但绝对不是平均,一边一次这种。

  • 資深大佬 : silenzio

    @Chaox 客气了 共同进步

  • 主 資深大佬 : Chaox

    @heimeil 谢谢解答,完全理解了。

  • 資深大佬 : lewinlan

    ch 推满了就死锁了。建议仔细复习一下操作系统关于锁的原理。

  • 資深大佬 : walsh

    其实有了原子操作,你自己写个锁也没问题,为性能着想再加上等待唤醒

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具