【问题标题】:all goroutines are asleep - deadlock issues所有 goroutine 都处于休眠状态 - 死锁问题
【发布时间】:2021-04-05 23:01:59
【问题描述】:

我正在尝试使用两个 goroutine 在同步管理器 (1,2,3,4...) 中打印奇偶数,我很困惑为什么我的代码会导致死锁。请你帮我理解一下?

package main

import (
    "fmt"
    "sync"
)
var wg sync.WaitGroup

func odd(ch chan bool){
    i :=1
    for i<=10{
        <-ch
        fmt.Println(i)
        i+=2
        ch<-true
    }
    wg.Done()
}

func even(ch chan bool){
    i :=2
    for i<=10{
        <-ch
        fmt.Println(i)
        i+=2
        ch <- true
    }
    wg.Done()
}

func main() {
    ch :=make(chan bool)
    wg.Add(2)
    go even(ch)
    go odd(ch)
    ch <- true
    wg.Wait()

}

O/P: 1 2 3 4 5 6 7 8 9 10 致命错误:所有 goroutine 都处于休眠状态 - 死锁!

goroutine 1 [semacquire]: sync.runtime_Semacquire(0x5844a8) /usr/local/go-faketime/src/runtime/sema.go:56 +0x45 sync.(*WaitGroup).Wait(0x5844a0) /usr/local/go-faketime/src/sync/waitgroup.go:130 +0x65 main.main() /tmp/sandbox505861393/prog.go:37 +0xcf

goroutine 6 [chan send]: main.even(0xc00005e060) /tmp/sandbox505861393/prog.go:26 +0xc5 由 main.main 创建 /tmp/sandbox505861393/prog.go:34 +0x7f

当我更改 goroutine 的顺序时,o/p 开始以奇偶方式打印,我也很难理解这一点。非常感谢您的帮助,谢谢。

【问题讨论】:

  • 您知道通道上的发送和接收都被阻塞了吗?正如您的错误输出已经告诉您的那样,由于另一个 goroutine 已经终止并且没有人在通道上接收,因此其中一个 goroutine 被卡在发送中。
  • 作为旁注。你不应该在同一个 goroutine 的通道上发送和接收。这不是好的做法。

标签: go deadlock channel goroutine


【解决方案1】:

首先,您的两个 goroutine 需要从ch 频道接收才能开始工作

for i<=10{
        <-ch // wait until receive
        fmt.Println(i)
        i+=2
        ch <- true
    }

所以你向ch 频道发送一个值来让你的两个goroutines 工作

func main() {
    //...
    ch <- true
    //...
}

但这不会按预期工作,因为您的两个 goroutine 共享相同的 ch 频道。当ch &lt;- true中的ch &lt;- true执行时,只有其中一个可以接收,开始工作,并将值发送回ch通道。

之后,两个 goroutine 不断从ch 通道接收,开始工作并将值发送回通道

换句话说,两个 goroutine 使用ch 通道相互发送和接收值

for i<=10 {
    <-ch           // receive when main() executed at first time, after that receive from another goroutine
    fmt.Println(i) // start work
    i+=2           //
    ch <- true     // return back to the channel, another goroutine will receive it
}
wg.Done()

但是问题是当一个goroutine退出时,剩下的goroutine下班后仍然尝试发送到ch频道,但是没有receiver,导致死锁

【讨论】:

    【解决方案2】:

    如果频道中没有空间,在频道上发送也会阻塞。如果您在通道中使用缓冲区,则发送可以工作。仅使用 1 个:

    ch := make(chan bool, 1)
    

    现在您可以发送数据了,它不会因为未满而阻塞 goroutine。如果您再次发送而不读取,那么它将阻止发送调用,因为再次没有空间并且之前的值仍然没有被消耗。

    关于打印顺序:goroutine 没有先启动的顺序,go lang 规范也没有提到多个 goroutine 是否从一个通道接收数据,那么 one waiting first really gets it first.因此,如果需要,您需要添加额外的同步来维护订单。 Here is hint for ordering sync.

    下面是修改后的打印代码,但我更喜欢不同的方法。检查上面链接中的 ping - pong 示例,其中使用了 2 个单独的通道而不是一个。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func odd(wg *sync.WaitGroup, ch chan bool) {
        defer wg.Done()
        i := 1
        for i <= 10 {
            <-ch
            fmt.Println(i)
            i += 2
            ch <- true
        }
    }
    
    func even(wg *sync.WaitGroup, ch chan bool) {
        defer wg.Done()
        i := 2
        for i <= 10 {
            <-ch
            fmt.Println(i)
            i += 2
            ch <- true
        }
    
    }
    
    func main() {
        var wg sync.WaitGroup
        ch := make(chan bool, 1)
        defer close(ch)
        wg.Add(2)
        go even(&wg, ch)
        go odd(&wg, ch)
        ch <- true
        wg.Wait()
    
    }
    

    【讨论】:

    • 非常感谢,Abhijit,这是有道理的,
    猜你喜欢
    • 2013-11-22
    • 2012-09-06
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    相关资源
    最近更新 更多