请教一个协程死锁的问题

ying1234 2019-10-15 09:18:38
type goWorker struct {
// task is a job should be done.
task chan func()

}
func main() {
myf:=func(){
fmt.Println("aaa")
}
m:=&goWorker{
task:make(chan func()),
}
go func(){
m.task<-myf

}()

for f:=range m.task{
if f==nil{
return
}
f()
}

aa:
for{
goto aa
}
}
以上代码输出aaa后马上报死锁,这我可以理解的,除非在m.task<-myf后加一句close(m.task)才不会死锁,但是如果改一下代码,把chan读写换一下位置:
type goWorker struct {
// task is a job should be done.
task chan func()

}
func main() {
myf:=func(){
fmt.Println("aaa")
}
m:=&goWorker{
task:make(chan func()),
}
go func(){

for f:=range m.task{
if f==nil{
return
}
f()
}
}()


m.task<-myf
aa:
for{
goto aa
}
}
这样程序就正常了,不会死锁,但是我无法理解,为什么这情的写法不会死锁,我也没有close chan 啊,难道在主协程写chan,新开的协程里读取就不死锁,反过来就不行?请教一下怎么回事?是什么原理。
...全文
266 6 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
ying1234 2019-10-15
  • 打赏
  • 举报
回复
阻塞是死锁的必要条件,但不是充分条件
所有的goroutine阻塞才会死锁抛出panic

我不明白的地方就在这, 谢谢,这样知道了。
qybao 2019-10-15
  • 打赏
  • 举报
回复
把你的第一个程序改成
func main() {
myf:=func(){
fmt.Println("aaa")
}
m:=&goWorker{
task:make(chan func()),
}
go func(){

defer func() {
m.task<-myf
}()

for ;; {
fmt.Println("bbbb")
}

}()

f := <-m.task //main协程阻塞,但go协程依然运行,所以也不死锁,所以并不是main协程阻塞就会死锁
f()

aa:
for{
goto aa
}
}
qybao 2019-10-15
  • 打赏
  • 举报
回复
因为main goroutine没有阻塞 阻塞是死锁的必要条件,但不是充分条件 所有的goroutine阻塞才会死锁抛出panic 可以参考以下帖子 https://grokbase.com/t/gg/golang-china/12c59nn0zj/gocn-7510-如何定位死锁或者阻塞的goroutine 第一种情况,往chan输出的go协程执行一遍就结束了,只剩下main协程,所以main协程一阻塞就满足全部协程阻塞的条件,所以就panic了 而第二种go协程阻塞了,main协程没阻塞,不满足panic条件,所以可以继续运行
ying1234 2019-10-15
  • 打赏
  • 举报
回复
引用 2 楼 qybao 的回复:
你的理解不对

你把第一种的
for f:=range m.task{
if f==nil{
return
}
f()
}
改成
f := <-m.task //只读取一次chan
f()
运行一下你就知道了

for f:=range m.task 会一直循环读取chan的输入,但是chan只有一次输入,所以就发生死锁


你再把上面的for恢复,然后把
go func(){
m.task<-myf
}()
改成
go func(){
for ;; {
m.task<-myf //一直往chan输出
}
}()
你说的这个可以理解,我不明白的是:
myf:=func(){
fmt.Println("aaa")
}
m:=&goWorker{
task:make(chan func()),
}
go func(){
for f:=range m.task{
if f==nil{
return
}
f()
}

}()
m.task<-myf
aa:
for{
goto aa
}
同样chan只输入一次,但是新开协程里for也是一直循环读取chan的输入,为什么这种情况程序就正常,输出了aaa,不会报死锁,很明显for读取了一次chan,执行了f()后,第二次读的时候就阻塞在那了,为什么不报死锁呢?chan只输入一次啊。
qybao 2019-10-15
  • 打赏
  • 举报
回复
你的理解不对

你把第一种的
for f:=range m.task{
if f==nil{
return
}
f()
}
改成
f := <-m.task //只读取一次chan
f()
运行一下你就知道了

for f:=range m.task 会一直循环读取chan的输入,但是chan只有一次输入,所以就发生死锁


你再把上面的for恢复,然后把
go func(){
m.task<-myf
}()
改成
go func(){
for ;; {
m.task<-myf //一直往chan输出
}
}()
ying1234 2019-10-15
  • 打赏
  • 举报
回复
按照我的理解,我只能这样解释,只有阻塞了main主协程,才会死锁。第1种情况是阻塞了main主协程,所以死锁了,第2种情况main主协程没有被阻塞,是新开的go func(){}协程里被阻塞了,所以不会死锁,只是读取后停下等待了,不知道这样理解对不对?

2,348

社区成员

发帖
与我相关
我的任务
社区描述
go语言学习与交流版
社区管理员
  • go语言社区
  • 俺叫西西弗斯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧