You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言多通道链式调用出现死锁问题排查

问题分析与解决

死锁原因

死锁的核心是通道未正确关闭,导致goroutine无限阻塞:

  1. 第一个goroutine启动了n个子goroutine发送状态消息到statusChan,但它启动完子goroutine就直接退出,既没等待所有子goroutine完成,也没关闭statusChan。
  2. 第二个goroutine(处理状态→删除)用range遍历statusChan,而range遍历通道的规则是:只有通道被关闭且内部所有数据被消费完,才会退出循环。由于statusChan没被关闭,这个goroutine在消费完所有已发送的状态消息后,会一直阻塞等待新消息,永远不会退出。
  3. 第三个goroutine(处理删除→done)同样用range遍历deleteChan,因为第二个goroutine一直没退出,也不会关闭deleteChan,所以它在消费完所有删除消息后,也会阻塞等待新消息,永远不会往done通道发送信号。
  4. 主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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 18:40:38