《Go程序设计语言》中goroutine与channel相关死锁问题求解
Go channel死锁触发原因解答
第243页代码去掉go关键字后的死锁触发逻辑
首先明确两个前提:
- 代码中的
worklist和unseenLinks均为无缓冲channel,发送、接收操作必须双方同时就绪才能完成,否则执行操作的goroutine会直接阻塞 - 去掉
go关键字后,原来的异步发送逻辑go func() { worklist <- foundLinks }()变成了在爬取goroutine内同步执行worklist <- foundLinks
死锁的触发流程如下:
- 程序启动后,初始命令行参数被送入worklist,主goroutine从worklist拿到初始链接列表,去重后往
unseenLinks发送未爬取过的链接 - 20个爬取goroutine从
unseenLinks拿到链接执行crawl操作,爬取完成后需要往worklist发送新抓到的链接列表;因为改成了同步发送,所以这个操作会直接阻塞当前爬取goroutine,直到主goroutine从worklist接收数据才能解除阻塞 - 当所有20个爬取goroutine都进入「等待往worklist发送数据」的阻塞状态时,就没有任何goroutine在监听、接收
unseenLinks的数据了 - 此时主goroutine还在处理之前的链接列表,遇到下一个未爬取过的链接时,会执行
unseenLinks <- link,但没有空闲的爬取goroutine接收这个发送请求,主goroutine直接阻塞在这一步 - 最终所有goroutine都陷入阻塞:主goroutine等
unseenLinks的接收方,所有爬取goroutine等worklist的接收方,没有任何goroutine可以继续运行,死锁触发。
原来带go的写法不会触发死锁的原因是:发送worklist的操作放在了独立的临时goroutine中执行,爬取goroutine提交完发送任务就会立刻回到for range unseenLinks的监听状态,随时可以接收主goroutine发来的新链接,不会出现所有爬取goroutine都被阻塞的情况。
内容的提问来源于stack exchange,提问作者unifreak
相关产品推荐
相关产品推荐

