请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

为什么我这个代码必定触发死锁啊?

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

麻烦老师解答下哇,学的一知半解的。

正在回答 回答被采纳积分+3

1回答

懶大蟲 2022-07-05 15:17:42

在理解这个问题前,要先知道读写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)
}()
2 回复 有任何疑惑可以回复我~
  • time.After(time.Millisecond*100):把等待时间加长一点也可以
    回复 有任何疑惑可以回复我~ 2022-07-23 23:58:05
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信