goroutine死锁问题

xiaoxi2017 · 2019-03-18 10:32:36 · 1620 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2019-03-18 10:32:36 的主题,其中的信息可能已经有所发展或是发生改变。

package main

import ( "fmt" )

var complete chan string = make(chan string)

func printstring(i int) { fmt.Println("打印这里", i) complete <- "妥了" }

func main() {

go printstring(1)
msg := <-complete
fmt.Println(msg)
printstring(2)

}

已经有一写一读,为什么还会死锁?怎么样才能不死锁呢?


有疑问加站长微信联系(非本文作者)

第 1 条附言  ·  2019-03-22 16:08:47

上面的兄弟说无缓冲区有写入必须有读取这个意思的,那要是我只在main中执行:go printstring(1) time.Sleep(3 * time.Second) 这样还是正常执行的 又怎么理解?

第 2 条附言  ·  2019-03-22 16:12:40

看了下面的分析感觉快理解了,再多测试几下发现又蒙蔽了

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1620 次点击  
加入收藏 微博
17 回复  |  直到 2019-03-29 18:59:15
a7505553
a7505553 · #1 · 6年之前

select了解一哈

jarlyyn
jarlyyn · #2 · 6年之前

因为你没异步

所以你的读和写只能按顺序。

legendlzy
legendlzy · #3 · 6年之前

你最后一行只有一个写,肯定死锁啊

alfred
alfred · #4 · 6年之前

应该不会吧

jiabaozhanglixl
jiabaozhanglixl · #5 · 6年之前

似乎你发现了GO的一个bug。 这样也会报错 complete <- "aaa1" msg := <-complete fmt.Println(msg) complete <- "aaa2" msg = <-complete fmt.Println(msg) 理论分析,不应该出现错误的。 同一个协程,先写再读,不应该报错的。 但是,实际运行,确实报这个错误。 难道channel必须在不同协程之间读写? 但是,channel加上缓冲区,var complete chan string = make(chan string,1),就不报错了。 似乎不带缓冲区的channel只支持不同协程之间读写? 期待高手来解答。

jiabaozhanglixl
jiabaozhanglixl · #6 · 6年之前
legendlzylegendlzy #3 回复

你最后一行只有一个写,肯定死锁啊

不是只有写没有读的问题。后面加上读,也会报错。 似乎是不带缓冲区的channel,用法特殊,只支持不同协程之间读写。或者根本就是GO的一个bug。 make(chan string,1),就不会报错。 很怪异。

DyingLight123
DyingLight123 · #7 · 6年之前

#3楼 @legendlzy 不是只有写没有读的问题。后面加上读,也会报错。 似乎是不带缓冲区的channel,用法特殊,只支持不同协程之间读写。或者根本就是GO的一个bug。 make(chan string,1),就不会报错。 很怪异。

正解

jarlyyn
jarlyyn · #8 · 6年之前

似乎你发现了GO的一个bug。 这样也会报错 complete <- "aaa1" msg := <-complete fmt.Println(msg) complete <- "aaa2" msg = <-complete fmt.Println(msg) 理论分析,不应该出现错误的。 同一个协程,先写再读,不应该报错的。 但是,实际运行,确实报这个错误。 难道channel必须在不同协程之间读写? 但是,channel加上缓冲区,var complete chan string = make(chan string,1),就不报错了。 似乎不带缓冲区的channel只支持不同协程之间读写? 期待高手来解答。

缓冲区满了自然死锁啊,没看出有什么地方有bug.

tianyutaizi
tianyutaizi · #9 · 6年之前

基础不扎实

概念: 无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应通道上执行接收操作时,这时值传送完成,2个goroutine都可以执行

看懂了上面的话,就不难发现printstring(2)阻塞了main进程,去掉该行即可

websong
websong · #10 · 6年之前

把最后一行去掉就可以了,你的complete 是无缓冲通道 ,最后一行 printstring(2) 只有发送,没有接受,所以阻塞了

jiabaozhanglixl
jiabaozhanglixl · #11 · 6年之前
jarlyynjarlyyn #8 回复

#5楼 @jiabaozhanglixl 缓冲区满了自然死锁啊,没看出有什么地方有bug.

这个看你怎么理解了。 所以我说的是似乎,并没有说就是。 按照正常的理解,既然是一个通道,当然是发送接收而已,没那么多复杂的。同一个协程,我发了再收,应该也说得通,逻辑不矛盾。但是实际就是不行,和常规理解不同。 你当然可以说GO就是这样规定的,必须一个协程发,另一个协程收,没毛病。但这确实是不大合乎常规的理解。并且,显然,这里看出,带缓冲区和不带缓冲区,都是通道,存在一个很大区别。不注意,很可能犯这个错误。 所以,这个例子看出,尽量用带缓冲区的channel,哪怕是长度是1的缓冲区,也更不容易出错。按照常规理解,长度是1的缓冲区,就是一次只能发一个,实际就是和不带缓冲区的意思一样,当然这个是常规理解。但GO就是不按常规理解来,这个也不是GO的错。但这样,似乎欠缺点什么,至少不够友好。

jiabaozhanglixl
jiabaozhanglixl · #12 · 6年之前

楼主这个例子,提醒我们,以后尽量用长度为1的通道,来代替无缓冲区通道,实际两个通道的作用都相同,但前者的坑更少。

jarlyyn
jarlyyn · #13 · 6年之前

问题这里锁死的根本原因就是缓存区满了啊。

缓存区为0,无法写入,满了。

那如果缓存区为1,结果写入了2个数据,一样满了,也会死锁啊。

所以你就算缓存区部位0,也不能解决问题,反而使得问题隐藏的更深,带来跟多的问题。

这个不光实在体现在chan上,实际socket之类的链接都会有同样的问题

随便搜索下,比如

http://wiki.jikexueyuan.com/project/java-socket/socket-tcp-deadlock.html

http://www.voidcn.com/article/p-cbjwyyjf-btm.html

niexinbi
niexinbi · #14 · 6年之前

似乎你发现了GO的一个bug。 这样也会报错 complete <- "aaa1" msg := <-complete fmt.Println(msg) complete <- "aaa2" msg = <-complete fmt.Println(msg) 理论分析,不应该出现错误的。 同一个协程,先写再读,不应该报错的。 但是,实际运行,确实报这个错误。 难道channel必须在不同协程之间读写? 但是,channel加上缓冲区,var complete chan string = make(chan string,1),就不报错了。 似乎不带缓冲区的channel只支持不同协程之间读写? 期待高手来解答。

这个不算错误吧

xiaoxi2017
xiaoxi2017 · #15 · 6年之前

楼主这个例子,提醒我们,以后尽量用长度为1的通道,来代替无缓冲区通道,实际两个通道的作用都相同,但前者的坑更少。

你说的有道理 呵呵

zhenggang
zhenggang · #16 · 6年之前

make(chan string)和make(chan string,1)是两个不同的概念,前者是缓冲区长度为0,1个都存不了,所以写入时会阻塞。后者是缓冲区长度为1,能存一个,所以后者不阻塞。

BruceWangNo1
BruceWangNo1 · #17 · 6年之前

“已经有一写一读,为什么还会死锁?怎么样才能不死锁呢?” 代码实现并没有一写一读。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传