如何从多个Goroutine中获取返回值?解决通道阻塞问题
关于Goroutine返回值捕获与Worker Pools通道取值的问题
一、多Goroutine用sync.WaitGroup+无缓冲通道导致永久阻塞的问题
问题原因
你遇到的永久阻塞,大概率是以下两种情况:
- 发送操作无对应接收:无缓冲通道的
send操作必须在有receive操作等待时才能完成。如果主goroutine先调用wg.Wait()等待所有子goroutine结束,而子goroutine还在等待通道接收,就会形成死锁——主goroutine等子goroutine完成,子goroutine等通道接收。 - 通道未关闭且接收次数不足:如果主goroutine只接收部分结果,剩余的子goroutine发送结果时会阻塞;或者接收完所有结果后未关闭通道,导致后续操作无意义阻塞。
正确实现示例
下面是适配多goroutine的写法,核心是在所有goroutine启动后接收结果,同时用单独goroutine等待任务完成后关闭通道:
package main import ( "fmt" "net/http" "sync" ) func fetchBodyLength(url string, wg *sync.WaitGroup, resultChan chan<- int) { defer wg.Done() resp, err := http.Get(url) if err != nil { resultChan <- 0 return } defer resp.Body.Close() resultChan <- int(resp.ContentLength) } func main() { urls := []string{ "https://example.com", "https://golang.org", "https://github.com", } var wg sync.WaitGroup resultChan := make(chan int) // 无缓冲通道也可正常工作 for _, url := range urls { wg.Add(1) go fetchBodyLength(url, &wg, resultChan) } // 单独goroutine等待所有任务完成后关闭通道 go func() { wg.Wait() close(resultChan) }() // 遍历通道接收所有结果 for length := range resultChan { fmt.Println("Body length:", length) } }
二、Worker Pools示例中直接用fmt.Println(<-results)报错的问题
在Worker Pools示例中,直接调用fmt.Println(<-results)会报错或阻塞,原因如下:
- 通道未关闭:示例中
results通道不会主动关闭,当所有任务完成后通道仍处于打开状态。如果主goroutine只执行一次<-results,取到一个结果后,程序会因无退出条件而阻塞;如果通道里暂时没有结果,会直接阻塞。 - 接收次数不匹配:示例中每个任务都会发送一个结果到
results,若只接收一次,剩余结果会留在通道中,worker goroutine可能仍在运行(直到jobs通道关闭),最终导致程序无法正常退出。
如果想手动接收结果,需确保:
- 接收次数与任务数量一致
- 在所有worker完成后关闭
results通道
修改后的主goroutine示例:
func main() { jobs := make(chan int, 5) results := make(chan int, 5) // 启动3个worker for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 发送5个任务 for j := 1; j <= 5; j++ { jobs <- j } close(jobs) // 接收所有5个结果 for a := 1; a <= 5; a++ { fmt.Println(<-results) } close(results) }
内容的提问来源于stack exchange,提问作者SonyMag
相关产品推荐
相关产品推荐

