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

使用Go协程与通道时出现“所有协程休眠-死锁”错误的原因排查

Go死锁问题:"all goroutines are asleep - deadlock!" 原因分析与修复

Hey there! Let's break down exactly why you're hitting that frustrating deadlock error in your Go code. First, let's fill in the missing sendString function (since it's referenced in your code snippet) to make the full picture clear:

package main

import "fmt"

func main() {
	var chans []chan string
	for i := 0; i < 3; i++ {
		chans = append(chans, make(chan string))
	}
	for i := 0; i < 3; i++ {
		go sendString(chans[i])
	}
	for str := range chans[0] {
		fmt.Print(str)
	}
}

func sendString(ch chan string) {
	// 假设这里是发送字符串的逻辑,比如发送一条消息
	ch <- "hello from goroutine"
}

问题根源

Let's walk through the deadlock step by step:

  1. 无缓冲通道的特性:你创建了3个无缓冲通道。无缓冲通道要求发送方和接收方同时就绪才能完成数据传递,否则会一直阻塞。
  2. 发送与接收不匹配:你启动了3个goroutine分别向3个通道发送数据,但只有chans[0]有对应的接收逻辑(main里的for range循环)。向chans[1]和chans[2]发送数据的goroutine会永远阻塞,因为没有任何代码在监听这两个通道。
  3. 主goroutine无限阻塞:就算另外两个goroutine的问题不存在,main里的for range循环在读取完chans[0]的单个值后,也会一直等待下去。因为for range遍历通道的逻辑是直到通道被关闭才会退出,而你的sendString函数从未关闭通道,导致主goroutine一直卡在等待更多数据的状态。

Go运行时检测到所有goroutine都陷入了阻塞(主goroutine卡在chans[0],另外两个卡在发送操作),就会抛出"all goroutines are asleep - deadlock!"错误。

修复方案

这里有两种常见的修复方式,取决于你的实际需求:

方案一:处理所有通道的接收

如果你需要读取所有三个通道的数据,可以用sync.WaitGroup来协调所有接收goroutine,同时让发送方在完成发送后关闭通道:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var chans []chan string
	var wg sync.WaitGroup

	// 创建通道并启动对应接收goroutine
	for i := 0; i < 3; i++ {
		ch := make(chan string)
		chans = append(chans, ch)
		wg.Add(1)
		go func(receiveChan chan string) {
			defer wg.Done()
			// 读取通道数据直到通道关闭
			for str := range receiveChan {
				fmt.Printf("%s ", str)
			}
		}(ch)
	}

	// 启动发送goroutine
	for i := 0; i < 3; i++ {
		go sendString(chans[i], i)
	}

	// 等待所有接收goroutine完成
	wg.Wait()
}

func sendString(ch chan string, id int) {
	ch <- fmt.Sprintf("hello from goroutine #%d", id)
	close(ch) // 发送完成后关闭通道,告知接收方没有更多数据
}

方案二:仅使用单个通道(如果这是你的意图)

如果你本来就只打算向chans[0]发送数据,那就不要为另外两个通道启动goroutine,同时记得在发送完成后关闭通道:

package main

import "fmt"

func main() {
	var chans []chan string
	for i := 0; i < 3; i++ {
		chans = append(chans, make(chan string))
	}

	// 仅启动对应chans[0]的发送goroutine
	go sendString(chans[0])

	// 读取数据直到通道关闭
	for str := range chans[0] {
		fmt.Print(str)
	}
}

func sendString(ch chan string) {
	ch <- "hello from goroutine"
	close(ch) // 关闭通道,让接收方知道可以退出循环
}

关键要点

  • 无缓冲通道需要发送方和接收方同时就绪才能完成数据传递,否则阻塞的goroutine会一直等待。
  • 确保每一个发送操作都有对应的接收逻辑(或者使用有缓冲通道来在没有即时接收方的情况下暂存数据)。
  • 当你完成数据发送后,记得调用close()关闭通道——这会告知接收方没有更多数据,避免接收方无限阻塞。

内容的提问来源于stack exchange,提问作者YuanZheng Hu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:36:13