为什么我这个代码必定触发死锁啊?
来源:11-8 并发任务的控制

weixin_慕先生3458425
2022-07-05
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan struct{})
m1 := msgGen("m1", done)
for i := 0; i < 5; i++ {
if v, ok := timeoutWait(m1, time.Microsecond*500); ok {
fmt.Println(v)
} else {
fmt.Println("m not receive msg")
}
}
done <- struct{}{}
<-done
}
func timeoutWait(c chan string, timeOut time.Duration) (string, bool) {
select {
case m := <-c:
return m, true
case <-time.After(timeOut):
return "", false
}
}
func msgGen(s string, done chan struct{}) chan string {
c := make(chan string)
go func() {
i := 0
for {
select {
case <-time.After(time.Millisecond):
c <- fmt.Sprintf("msger %s receive %d", s, i)
case <-done:
fmt.Println("Cleaning")
time.Sleep(time.Second)
fmt.Println("Clean done")
done <- struct{}{}
return
}
i++
}
}()
return c
}
运行结果:
m not receive msg
msger m1 receive 0
m not receive msg
msger m1 receive 1
m not receive msg
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.msgGen.func1()
C:/Users/Administrator/go/src/go-tour/imooc/180/并发任务的控制/main.go:38 +0xed
created by main.msgGen
C:/Users/Administrator/go/src/go-tour/imooc/180/并发任务的控制/main.go:33 +0xb9
exit status 2
麻烦老师解答下哇,学的一知半解的。
写回答
1回答
-
懶大蟲
2022-07-05
在理解这个问题前,要先知道读写channel的操作是阻塞的,对一个channel进行写(读)时,当前的goroutine会被阻塞住,直到这个channel在另一个goroutine中被读(写)。
所以当main()执行到18行,也就是done <- struct{}{}时,main()会阻塞,它要等其他的goroutine去读掉这个done,这很容易理解。
出现死锁的地方是38行,也就是
c <- fmt.Sprintf("msger %s receive %d", s, i)
由上述阻塞的知识我们知道,从33行开始直到48行结束的这段goroutine会在这里被阻塞住,等待其他goroutine从c中读取它写入的消息。
当main()结束了5次循环,准备发送done,也就是将要执行18行时,已没有任何goroutine会再去读取那个c channel。
由于goroutine是并行的,那么极有可能出现这种情况:main()还没来得及执行18行,从33行开始的那段goroutine在又一次循环中执行到了38行(没有收到done,因为main还没来得及发),于是乎38行阻塞了,等待消费,永远无法进入下一次循环去读取done。而之后main()执行到18行也阻塞住了,因为它发送的done也永远没人有机会读了。
至此,形成了33等待main去读c,而main等待33读done的情况,形成死锁。
解决这个死锁也不难,你可以将38行包在一个goroutine里,这样就不会阻塞33这个goroutine的执行了
go func() { c <- fmt.Sprintf("msger %s receive %d", s, i) }()
212022-07-23
相似问题