【问题标题】:Why is this Go code deadlocking?为什么这个 Go 代码会死锁?
【发布时间】:2024-04-18 12:10:01
【问题描述】:
package main

import "fmt"
import "runtime"
import "time"


func check(id int) {
    fmt.Println("Checked", id)
    <-time.After(time.Duration(id)*time.Millisecond)
    fmt.Println("Woke up", id)
}

func main() {
    defer runtime.Goexit()

    for i := 0; i <= 10; i++ {
        fmt.Println("Called with", i)
        go check(i)
    }

    fmt.Println("Done for")
}

我对 Go 很陌生,所以任何指针都会很棒。我将如何调试这样的事情?

你可以运行 sn -p http://play.golang.org/p/SCr8TZXQUE

更新:这在操场上没有&lt;-time.After(time.Duration(id)*time.Millisecond) 行有效,我想知道为什么? (正如@dystroy 提到的,这可能是因为操场处理时间的方式)

当我在本地尝试时,这是输出:

Called with  0
Called with  1
Checked 0
Called with  2
Checked 1
Called with  3
Checked 2
Called with  4
Woke up 0
Checked 3
Called with  5
Checked 4
Called with  6
Checked 5
Called with  7
Checked 6
Called with  8
Checked 7
Called with  9
Checked 8
Called with  10
Checked 9
Woke up 1
Done for
Checked 10
Woke up 2
Woke up 3
Woke up 4
Woke up 5
Woke up 6
Woke up 7
Woke up 8
Woke up 9
Woke up 10
throw: all goroutines are asleep - deadlock!

goroutine 2 [syscall]:
created by runtime.main
    /tmp/bindist046461602/go/src/pkg/runtime/proc.c:221

goroutine 5 [timer goroutine (idle)]:
created by addtimer
    /tmp/bindist046461602/go/src/pkg/runtime/ztime_amd64.c:69
exit status 2

所有的 goroutine 都完成了,但无论如何都会引发死锁。我应该注意,如果使用计时器并不重要,无论哪种方式都会死锁。

【问题讨论】:

    标签: concurrency go deadlock goroutine


    【解决方案1】:

    来自the documentation of Goexit

    Goexit 终止调用它的 goroutine。没有其他 goroutine 受到影响。 Goexit 在终止 goroutine 之前运行所有延迟调用。

    您正在退出主程序。不。当您这样做时,在您使用go check(i) 启动的最后一个程序完成后,没有任何例程运行,因此出现“死锁”。只需删除此行:

    defer runtime.Goexit()
    

    如果你想要在 main 中等待一组 goroutine 完成,你可以使用 sync.WaitGroup

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func check(id int, wg *sync.WaitGroup) {
        fmt.Println("Checked", id)
        <-time.After(time.Duration(id)*time.Millisecond)
        fmt.Println("Woke up", id)
        wg.Done()
    }
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i <= 10; i++ {
            wg.Add(1)
            fmt.Println("Called with", i)
            go check(i, &wg)
        }
        wg.Wait()
        fmt.Println("Done for")
    }
    

    编辑:

    如果你在 golang 的 Playground 上测试它,任何 time.After 都会死锁,因为时间在 Playground 中被冻结,Goexit 可能会退出一个标准程序中甚至不存在的例程。

    【讨论】:

    • runtime.Goexit() 我正在等待其他goroutines 完成,它似乎工作。我的意思是所有的 goroutine 都运行 fmt.Println("Woke up") 行,但它仍然检测到死锁。没有这条线,它不会等待其他 goroutine 完成。
    • 这是例程的正常行为。你想做什么 ?你想让所有的 goroutine 在你打印“done for”之前完成吗?
    • 不,我希望 main() 等待其他 goroutine 完成。
    • 这在没有time.After 行的情况下有效。我想知道为什么会导致死锁。它应该只是阻止,对吧?
    • 感谢 sn-p。我读到了WaitGroup,但我真的很想知道为什么上面的 sn-p 没有&lt;-time.After 行。
    【解决方案2】:

    你所有的 goroutine 都在等待某人消费他们在&lt;-time.After 中发送的值。 你可以删除 &lt;- 或者让 main 消耗你启动的所有 goroutine 的值。

    编辑

    这对我有用

    package main
    
    import "fmt"
    //import "runtime"
    import "time"
    
    
    func check(id int) {
        fmt.Println("Checked", id)
        <-time.After(time.Duration(id)*time.Millisecond)
        fmt.Println("Woke up", id)
    }
    
    func main() {
        //defer runtime.Goexit()
    
        for i := 0; i <= 10; i++ {
            fmt.Println("Called with", i)
            go check(i)
        }
    
        fmt.Println("Done for")
    }
    

    witch 与之前有人提出的解决方案相同,所以我将提出一个没有等待组的解决方案

    package main
    
    import "fmt"
    import "time"
    
    
    func check(id int, c chan bool) {
        fmt.Println("Checked", id)
        time.After(time.Duration(id)*time.Millisecond)
        fmt.Println("Woke up", id)
        c <- true
    }
    
    func main() {
        c := make(chan bool)
    
        for i := 0; i <= 10; i++ {
            fmt.Println("Called with", i)
            go check(i, c)
        }
        var n uint
        for n<10 {
            <- c
            n++
        }
        fmt.Println("Done for")
    }
    

    【讨论】:

    • 该值将被丢弃并垃圾收集 AFAIK。正如我所说的,我保留这条线或删除这条线都没有关系,它仍然被检测为死锁。
    • 你是对的。我对其进行了测试,结果你必须不调用 Goexit 否则它不会被垃圾收集。
    最近更新 更多