V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
v2defy
V2EX  ›  Go 编程语言

go 语言的一个死锁问题

  •  
  •   v2defy · 2021-06-03 18:47:54 +08:00 · 1713 次点击
    这是一个创建于 1298 天前的主题,其中的信息可能已经有所发展或是发生改变。

    两个 goroutine,一个输出奇数,一个输出偶数,交替输出,最终达到顺序输出的目的。

    其中 B 协程运行到最后的时候,如果不做特殊的处理,继续向 A 通道写入数据,会导致 A 通道死锁。

    感到很奇怪,为什么会这样? A 通道这时候是没有数据的,为什么不让写?

    package main
    
    import (
      "fmt"
    )
    
    func main() {
      // 创建 3 个 channel,A,B 和 Exit
      A := make(chan bool)
      B := make(chan bool)
      Exit := make(chan bool)
    
      go func() {
        // 如果 A 通道是 true,我就执行
        for i := 1; i <= 10; i += 2 {
          if ok := <-A; ok {
            fmt.Println("A 输出", i)
            B <- true
          }
        }
      }()
    
      go func() {
        defer func() { Exit <- true }() // 这个协程的活干完之后,向主 goroutine 发送信号
        // 如果 B 通道是 true,我就执行
        for i := 2; i <= 10; i += 2 {
          if ok := <-B; ok {
            fmt.Println("B 输出", i)
            if i != 10 { // 如果 i 等于 10 了,就不要再向 A 通道写数据了,否则将导致 A 通道死锁,至于为什么,坦白说我很疑惑
              A <- true
            }
          }
        }
      }()
    
      A <- true // 启动条件
      <-Exit    // 结束条件
    }
    
    7 条回复    2021-06-04 09:31:52 +08:00
    darrh00
        1
    darrh00  
       2021-06-03 19:01:53 +08:00   ❤️ 1
    第二个 goroutine 运行到 i = 10 的时候,第一个 goroutine 中的 for 循环已经结束退出了,因此 A 通道再也没有了接收者,而第二个 goroutine 里还继续向 A 通道写数据,当然就出错了。
    SteinsGate
        2
    SteinsGate  
       2021-06-03 19:03:13 +08:00 via Android
    因为第一个退出了,无缓冲区通道,没人消费 A,卡在了 A <- true
    v2defy
        3
    v2defy  
    OP
       2021-06-03 19:05:35 +08:00
    @darrh00 原来是这样!!感谢大佬,一句话就解答了我的疑惑。
    v2defy
        4
    v2defy  
    OP
       2021-06-03 19:06:11 +08:00
    @SteinsGate 谢谢大佬,懂了
    GTim
        5
    GTim  
       2021-06-03 19:17:33 +08:00
    给你换种方式输出结果你就知道了

    ```go
    package main

    import (
    "fmt"
    )

    func main() {
    // 创建 3 个 channel,A,B 和 Exit
    A := make(chan bool)
    B := make(chan bool)
    Exit := make(chan bool)

    go func() {
    // 如果 A 通道是 true,我就执行
    for i := 1; i <= 10; i += 2 {
    if ok := <-A; ok {
    fmt.Printf("-> A(%d)", i)
    B <- true
    }
    }
    fmt.Print("===bye==")
    }()

    go func() {
    defer func() { Exit <- true }() // 这个协程的活干完之后,向主 goroutine 发送信号
    // 如果 B 通道是 true,我就执行
    for i := 2; i <= 10; i += 2 {
    if ok := <-B; ok {
    fmt.Printf("-> B(%d)", i)
    //if i != 10 { // 如果 i 等于 10 了,就不要再向 A 通道写数据了,否则将导致 A 通道死锁,至于为什么,坦白说我很疑惑
    A <- true
    //}
    }
    }
    fmt.Print("===bye==")
    }()

    fmt.Print("|")
    A <- true // 启动条件
    <-Exit // 结束条件
    }

    ```

    输出结果如下

    ```
    |-> A(1)-> B(2)-> A(3)-> B(4)-> A(5)-> B(6)-> A(7)-> B(8)-> A(9)===bye==-> B(10)fatal error: all goroutines are asleep - deadlock!
    ```

    原因是不带缓冲区的 chan 写入时立刻会被堵塞。看看官方怎么对无缓冲区 chan 的描述就知道了
    v2defy
        6
    v2defy  
    OP
       2021-06-03 19:34:59 +08:00
    @GTim 细节啊!学费了学费了^_^
    george404
        7
    george404  
       2021-06-04 09:31:52 +08:00
    还有,我建议用 waitgroup 来同步不同的 thread. 这样看上去也比较清晰。个人建议啦。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   889 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 20:17 · PVG 04:17 · LAX 12:17 · JFK 15:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.