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

Go协程向多通道写入时触发死锁(Fatal error: all goroutines are asleep deadlock)问题求助

Go协程向多通道写入时触发死锁(Fatal error: all goroutines are asleep deadlock)问题求助

兄弟,我看了你的代码,问题出在无缓冲通道的阻塞特性和WaitGroup的调用顺序上,咱们一步步拆解清楚:

核心问题分析

你创建的evenChannel和oddChannel是无缓冲通道(make(chan int)默认就是无缓冲),这种通道的发送操作会一直阻塞,直到有接收者准备好接收数据。但你的main函数逻辑是:

  1. 先启动生产者协程generateNumbers
  2. 立刻调用wg.Wait()等待生产者完成
  3. 等生产者完成后才开始接收通道数据

这就导致了死锁:生产者协程在往通道写第一个数的时候,因为没有任何接收者,直接被阻塞住,永远执行不到defer wg.Done();而main函数卡在wg.Wait()这里,永远等不到信号,最终所有协程都休眠,触发死锁错误。

另外,你原代码里关闭通道的位置也不对——通道应该由生产者在写完所有数据后关闭,告诉消费者“没有更多数据了”,而不是在消费者接收完之后才关闭。

修改后的解决方案

这里给你两种常用的写法,都能解决问题:

方案一:用独立消费者协程 + WaitGroup(清晰直观)

package main

import (
	"fmt"
	"sync"
)

func generateNumbers(evenChannel, oddChannel chan<- int) {
	for i := 1; i <= 50; i++ {
		if i%2 == 1 {
			oddChannel <- i
		} else {
			evenChannel <- i
		}
	}
	// 生产者写完所有数据后,主动关闭通道
	close(evenChannel)
	close(oddChannel)
}

// 单独的偶数处理协程
func printEven(wg *sync.WaitGroup, evenChannel <-chan int) {
	defer wg.Done()
	// range遍历通道,通道关闭后会自动退出循环
	for evenNum := range evenChannel {
		fmt.Println("Even Number:", evenNum)
	}
}

// 单独的奇数处理协程
func printOdd(wg *sync.WaitGroup, oddChannel <-chan int) {
	defer wg.Done()
	for oddNum := range oddChannel {
		fmt.Println("Odd Number:", oddNum)
	}
}

func main() {
	var wg sync.WaitGroup
	evenChannel := make(chan int)
	oddChannel := make(chan int)

	// 启动生产者协程
	go generateNumbers(evenChannel, oddChannel)

	// 启动两个消费者协程,等待它们处理完成
	wg.Add(2)
	go printEven(&wg, evenChannel)
	go printOdd(&wg, oddChannel)

	// 等待所有消费者处理完毕
	wg.Wait()
}

方案二:用匿名协程接收 + 信号通道(无需额外函数)

如果不想写单独的消费者函数,也可以用匿名协程配合信号通道来实现:

package main

import (
	"fmt"
)

func generateNumbers(evenChannel, oddChannel chan<- int) {
	for i := 1; i <= 50; i++ {
		if i%2 == 1 {
			oddChannel <- i
		} else {
			evenChannel <- i
		}
	}
	close(evenChannel)
	close(oddChannel)
}

func main() {
	evenChannel := make(chan int)
	oddChannel := make(chan int)

	go generateNumbers(evenChannel, oddChannel)

	// 用两个信号通道标记接收完成
	evenDone := make(chan struct{})
	oddDone := make(chan struct{})

	// 匿名协程处理偶数
	go func() {
		for num := range evenChannel {
			fmt.Println("Even Number:", num)
		}
		evenDone <- struct{}{}
	}()

	// 匿名协程处理奇数
	go func() {
		for num := range oddChannel {
			fmt.Println("Odd Number:", num)
		}
		oddDone <- struct{}{}
	}()

	// 等待两个接收任务都完成
	<-evenDone
	<-oddDone
}

关键总结

  1. 无缓冲通道的发送和接收必须同时准备好,否则会阻塞,所以一定要让接收逻辑和发送逻辑并发执行,不能等发送完再接收。
  2. 通道的关闭职责属于生产者,写完数据后关闭通道,消费者才能通过range或者ok判断知道没有更多数据了。

备注:内容来源于stack exchange,提问作者Pikachu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 14:25:28