为什么这段Go代码会发生死锁?

huangapple go评论92阅读模式
英文:

Why is this Go code deadlocking?

问题

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")

}

英文:
package main

import &quot;fmt&quot;
import &quot;runtime&quot;
import &quot;time&quot;


func check(id int) {
	fmt.Println(&quot;Checked&quot;, id)
	&lt;-time.After(time.Duration(id)*time.Millisecond)
	fmt.Println(&quot;Woke up&quot;, id)
}

func main() {
	defer runtime.Goexit()
	
	for i := 0; i &lt;= 10; i++ {
		fmt.Println(&quot;Called with&quot;, i)
		go check(i)
	}
	
	fmt.Println(&quot;Done for&quot;)
}

I'm very new to Go, so any pointers would be great. How would I go about debugging such a thing?

You can run the snippet http://play.golang.org/p/SCr8TZXQUE

update: this works without the line &lt;-time.After(time.Duration(id)*time.Millisecond) on playground, I want to know why? (As mentioned by @dystroy this maybe because of the way playground handles time)

When I try locally, this is the output:

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

All the goroutines complete but throws a deadlock anyway. I should note that it doesn't matter if the timer is used, deadlocks either way.

答案1

得分: 7

Goexit的文档中:

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

你正在退出主例程。不要这样做。因为在你最后一个使用go check(i)启动的例程完成之后,没有任何例程在运行,所以会出现“死锁”。只需删除这行代码:

defer runtime.Goexit()

如果你想在主例程中等待一组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可能会退出一个在标准程序中甚至不存在的例程。

英文:

From the documentation of Goexit :

> Goexit terminates the goroutine that calls it. No other goroutine is affected. Goexit runs all deferred calls before terminating the goroutine.

You're exiting the main routine. Don't. As you do it, there isn't any routine running after the last one you launched with go check(i) has finished, hence the "deadlock". Simply remove this line :

defer runtime.Goexit()

If what you want is to wait in main for a group of goroutines to finish, you may use a sync.WaitGroup :

package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

func check(id int, wg *sync.WaitGroup) {
    fmt.Println(&quot;Checked&quot;, id)
    &lt;-time.After(time.Duration(id)*time.Millisecond)
    fmt.Println(&quot;Woke up&quot;, id)
    wg.Done()
}

func main() {
	var wg sync.WaitGroup
    for i := 0; i &lt;= 10; i++ {
        wg.Add(1)
        fmt.Println(&quot;Called with&quot;, i)
        go check(i, &amp;wg)
    }
	wg.Wait()
    fmt.Println(&quot;Done for&quot;)
}

EDIT :

if you're testing it on golang's playground, any time.After will deadlock because time is frozen in playground and Goexit maybe exit a routine that doesn't even exist in a standard program.

答案2

得分: 0

all your goroutines are waiting for someone to consume the value they are sending in <-time.After.
you can just delete the <- or make main consume the value of all goroutines you launched.

Edit

this worked for me

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 is the same solution someone proposed before so I'll propose a solution without a waitgroup

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")
}
英文:

all your goroutines are waiting for someone to consume the value they are sending in &lt;-time.After.
you can just delete the &lt;- or make main consume the value of all goroutines you launched.

Edit

this worked for me

package main

import &quot;fmt&quot;
//import &quot;runtime&quot;
import &quot;time&quot;


func check(id int) {
    fmt.Println(&quot;Checked&quot;, id)
    &lt;-time.After(time.Duration(id)*time.Millisecond)
    fmt.Println(&quot;Woke up&quot;, id)
}

func main() {
    //defer runtime.Goexit()

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

    fmt.Println(&quot;Done for&quot;)
}

witch is the same solution someone proposed before so I'll propose a solution without a waitgroup

package main

import &quot;fmt&quot;
import &quot;time&quot;


func check(id int, c chan bool) {
    fmt.Println(&quot;Checked&quot;, id)
    time.After(time.Duration(id)*time.Millisecond)
    fmt.Println(&quot;Woke up&quot;, id)
    c &lt;- true
}

func main() {
    c := make(chan bool)

    for i := 0; i &lt;= 10; i++ {
        fmt.Println(&quot;Called with&quot;, i)
        go check(i, c)
    }
    var n uint
    for n&lt;10 {
    	&lt;- c
    	n++
    }
    fmt.Println(&quot;Done for&quot;)
}

huangapple
  • 本文由 发表于 2012年10月5日 19:56:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/12745771.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定