书 <The Go Programming Language> 中的死锁,它是如何发生的以及为什么会发生?
Deadlock in book <The Go Programming Language>, how it would happen and why it happen?
本书中多次提到deadlock
关于goroutine
和channel
的错误使用,我没能理解为什么会发生死锁。
我想说的第一件事是,我知道通道发送和接收将阻塞,直到有要读取的项目或要发送项目的房间,也许这就是某些 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)
}
}
}
}
引用书的第二段:
...the initial send of the command-line arguments to the worklist must run in its own goroutine to avoid deadlock, a stuck situation in which both the main goroutine and a crawler goroutine attempt to send to each other while neither is receiving...
假设初始发送到 worklist
不在它自己的 goroutin 中,我想它是这样工作的:
- 主 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
,死锁是如何发生的?
在第一个片段中,初始通道发送需要在 goroutine 中完成,因为如果没有 goroutine,语句将无限期地阻塞并且执行永远不会到达从该通道接收的循环。也就是说,要从 1.
到 2.
,1.
需要在 goroutine 中完成。但是,如果 1.
阻塞,则永远不会达到 2.
。
评论开始的地方就是执行停止的地方:
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)
// }
// }
// }
// }
在第二个片段中,您有一个 for-range
循环遍历一个通道,这样的循环将不会退出,直到 ranged-over 通道关闭。这意味着,虽然这种循环的主体可能会继续执行,但永远不会到达 loop-with-unclosed-channel 之后的代码。
https://golang.org/ref/spec#For_range
- For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel
is nil, the range expression blocks forever.
评论开始的地方就是执行停止的地方:
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 例程:
go func() { worklist <- foundLinks }()
Links found by crawl are sent to the worklist from a dedicated
goroutine to avoid deadlock.
为什么一定要存在这个?
本书中多次提到deadlock
关于goroutine
和channel
的错误使用,我没能理解为什么会发生死锁。
我想说的第一件事是,我知道通道发送和接收将阻塞,直到有要读取的项目或要发送项目的房间,也许这就是某些 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)
}
}
}
}
引用书的第二段:
...the initial send of the command-line arguments to the worklist must run in its own goroutine to avoid deadlock, a stuck situation in which both the main goroutine and a crawler goroutine attempt to send to each other while neither is receiving...
假设初始发送到 worklist
不在它自己的 goroutin 中,我想它是这样工作的:
- 主 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
,死锁是如何发生的?
在第一个片段中,初始通道发送需要在 goroutine 中完成,因为如果没有 goroutine,语句将无限期地阻塞并且执行永远不会到达从该通道接收的循环。也就是说,要从 1.
到 2.
,1.
需要在 goroutine 中完成。但是,如果 1.
阻塞,则永远不会达到 2.
。
评论开始的地方就是执行停止的地方:
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)
// }
// }
// }
// }
在第二个片段中,您有一个 for-range
循环遍历一个通道,这样的循环将不会退出,直到 ranged-over 通道关闭。这意味着,虽然这种循环的主体可能会继续执行,但永远不会到达 loop-with-unclosed-channel 之后的代码。
https://golang.org/ref/spec#For_range
- For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel is nil, the range expression blocks forever.
评论开始的地方就是执行停止的地方:
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 例程:
go func() { worklist <- foundLinks }()
Links found by crawl are sent to the worklist from a dedicated goroutine to avoid deadlock.
为什么一定要存在这个?