Go语言多通道链式调用出现死锁问题排查
问题分析与解决
死锁原因
死锁的核心是通道未正确关闭,导致goroutine无限阻塞:
- 第一个goroutine启动了
n个子goroutine发送状态消息到statusChan,但它启动完子goroutine就直接退出,既没等待所有子goroutine完成,也没关闭statusChan。 - 第二个goroutine(处理状态→删除)用
range遍历statusChan,而range遍历通道的规则是:只有通道被关闭且内部所有数据被消费完,才会退出循环。由于statusChan没被关闭,这个goroutine在消费完所有已发送的状态消息后,会一直阻塞等待新消息,永远不会退出。 - 第三个goroutine(处理删除→done)同样用
range遍历deleteChan,因为第二个goroutine一直没退出,也不会关闭deleteChan,所以它在消费完所有删除消息后,也会阻塞等待新消息,永远不会往done通道发送信号。 - 主goroutine一直在
<-done处等待,最终所有goroutine都进入休眠状态,触发死锁。
休眠的goroutine
- 第二个goroutine:阻塞在
for itemNum := range statusChan,等待statusChan的新数据(不会再有新数据)。 - 第三个goroutine:阻塞在
for itemNum := range deleteChan,等待deleteChan的新数据(不会再有新数据)。 - 主goroutine:阻塞在
<-done,等待done通道的信号。
修复方案
需要在所有状态消息发送完成后关闭statusChan,所有删除消息发送完成后关闭deleteChan,用sync.WaitGroup等待goroutine执行完毕:
修改后的代码:
package main import ( "fmt" "sync" "time" ) func main() { numbers := []int{1, 2, 4} for _, n := range numbers { fmt.Printf("Schedule and delete %d items.\n", n) statusChan := make(chan string, n) deleteChan := make(chan string, n) done := make(chan bool) var wgStatus sync.WaitGroup go func(n int, statusChan chan<- string) { wgStatus.Add(n) for i := 0; i < n; i++ { i := i go func() { defer wgStatus.Done() fmt.Printf("Scheduling item number ... %d\n", i) itemNum := fmt.Sprintf("item_num_%d\n", i) time.Sleep(500 * time.Millisecond) statusChan <- itemNum }() } // 等待所有子goroutine发送完状态,关闭statusChan wgStatus.Wait() close(statusChan) }(n, statusChan) var wgDelete sync.WaitGroup go func(statusChan <-chan string, deleteChan chan<- string) { for itemNum := range statusChan { wgDelete.Add(1) go func(item string) { defer wgDelete.Done() fmt.Printf("Checking status of item number ... %s\n", item) time.Sleep(500 * time.Millisecond) deleteChan <- item }(itemNum) } // 等待所有状态检查完成,关闭deleteChan wgDelete.Wait() close(deleteChan) }(statusChan, deleteChan) go func(deleteChan <-chan string, done chan<- bool) { for itemNum := range deleteChan { fmt.Printf("Delete item: %s", itemNum) time.Sleep(500 * time.Millisecond) } fmt.Printf("Done with scheduling and deleting %d items.\n", n) done <- true }(deleteChan, done) <-done } }
关键修改点:
- 用
sync.WaitGroup等待所有发送状态的子goroutine完成,关闭statusChan,让第二个goroutine的range循环正常退出。 - 用
sync.WaitGroup等待所有状态检查的子goroutine完成,关闭deleteChan,让第三个goroutine的range循环正常退出,最终向done发送信号,主goroutine继续执行。
内容的提问来源于stack exchange,提问作者Jonathan Kittell
相关产品推荐
相关产品推荐

