《Go 编程语言》一书中的死锁,它是如何发生的以及为啥会发生?
Posted
技术标签:
【中文标题】《Go 编程语言》一书中的死锁,它是如何发生的以及为啥会发生?【英文标题】:Deadlock in book <The Go Programming Language>, how it would happen and why it happen?《Go 编程语言》一书中的死锁,它是如何发生的以及为什么会发生? 【发布时间】:2021-11-10 05:11:26 【问题描述】:这本书中有好几次谈到deadlock
关于goroutine
和channel
的错误使用,我没能理解为什么会发生死锁。
我要说的第一件事是,我知道频道 send&receive 会阻塞,直到有要读取的项目或要发送项目的房间,这也许是某些 deadlock
的深层原因。但是请根据本书的以下摘录给我一些解释:
第 240 页
这段代码是并发爬取url,BFS风格:
func main()
worklist := make(chan []string)
// Start with the command-line arguments.
go func() worklist <- os.Args[1:] ()
// Crawl the web concurrently.
seen := make(map[string]bool)
for list := range worklist
for _, link := range list
if !seen[link]
seen[link] = true
go func(link string)
worklist <- crawl(link)
(link)
引用书的第二段:
...命令行参数到工作列表的初始发送必须在其自己的 goroutine 中运行以避免死锁,这是一种卡住的情况,主 goroutine 和爬虫 goroutine 都尝试相互发送,而两者都没有接收...
假设初始发送到 worklist
不在它自己的 goroutin 中,我想它是这样工作的:
-
main goroutine 发送初始值到
worklist
,阻塞直到收到
for range
接收初始项目,因此解封worklist
频道
爬虫 goroutine 将其项目发送到 worklist
,循环...
所以据我了解,它不会阻塞和死锁。我哪里错了?
更新:@mkopriva 帮助我意识到,由于第 1 步被阻止,第 2,3 步无法访问。所以我很清楚这一点。
第 243 页
这种死锁情况可能与第 240 页相同:
func main()
worklist := make(chan []string) // list of URLs, may have duplicates
unseenLinks := make(chan string) // de-duplicated URLs
// Add command-lin arguments to worklist.
go func() worklist <- os.Args[1:] ()
// Create 20 crawler goroutines to fetch each unseen link.
for i := 0; i < 20; i++
go func()
for link := range unseenLinks
foundLinks := crawl(link)
go func() worklist <- foundLinks ()
()
// The main goroutine de-duplicates worklist items
// and sends the unseen ones to the crawlers.
seen := make(map[string]bool)
for list := range worklist
for _, link := range list
if !seen[link]
seen[link] = true
unseenLinks <- link
那么,如果我在for-range
循环中省略go
,死锁是如何发生的?
【问题讨论】:
“我想它是这样工作的:....” -- 在你想象的执行模型中,如果1
阻塞你如何到达2
?
对...我才意识到这一点。我的心被堵住了。仍在处理第二个代码 sn-p。
如果不清楚,如果你从循环中删除 goroutine,第二个 sn-p 也会有同样的问题。 for link := range unseenLinks
循环不会退出,直到某事关闭 unseenLinks
.
@mkopriva 我明白了。谢谢!你能写下答案让我接受吗?
【参考方案1】:
在第一个 sn-p 中,初始通道发送需要在 goroutine 中完成,因为如果没有 goroutine,语句将无限期阻塞,并且执行永远不会到达从该通道接收的循环。即从1.
到2.
,1.
需要在 goroutine 中完成。但是,如果 1.
阻塞,则永远无法到达 2.
。
cmets 开始的地方就是执行停止的地方:
func main()
worklist := make(chan []string)
worklist <- os.Args[1:]
//
// seen := make(map[string]bool)
// for list := range worklist
// for _, link := range list
// if !seen[link]
// seen[link] = true
// go func(link string)
// worklist <- crawl(link)
// (link)
//
//
//
//
在第二个 sn-p 中,您有一个通道上的 for-range
循环,这样的循环在远程通道关闭之前不会退出。这意味着,虽然这样的循环体可能会继续执行,但永远不会到达 loop-with-unclosed-channel 之后的代码。
https://golang.org/ref/spec#For_range
对于通道,生成的迭代值是通道上发送的连续值直到通道为closed。如果频道 为 nil,范围表达式将永远阻塞。
cmets 开始的地方就是执行停止的地方:
func main()
worklist := make(chan []string)
unseenLinks := make(chan string)
go func() worklist <- os.Args[1:] ()
for i := 0; i < 20; i++
for link := range unseenLinks
// foundLinks := crawl(link)
// go func() worklist <- foundLinks ()
//
//
//
// seen := make(map[string]bool)
// for list := range worklist
// for _, link := range list
// if !seen[link]
// seen[link] = true
// unseenLinks <- link
//
//
//
//
【讨论】:
以上是关于《Go 编程语言》一书中的死锁,它是如何发生的以及为啥会发生?的主要内容,如果未能解决你的问题,请参考以下文章