Go并发操作中如何正确关闭channel以解决程序无法退出问题
修复方案
问题核心是:range遍历channel时会持续阻塞直到channel被关闭,原代码没有在所有goroutine完成写入后主动关闭channel,所以主协程会永久卡在遍历逻辑里。
正确的做法是用sync.WaitGroup监控所有发起的goroutine执行状态,等全部goroutine都完成向channel的写入操作后,再关闭channel,步骤如下:
- 引入
sync标准库,初始化WaitGroup实例 - 每启动一个
getLinkgoroutine前调用wg.Add(1)计数 - 在
getLink函数中用defer wg.Done()标记当前goroutine执行完成 - 单独启动一个goroutine等待所有
WaitGroup计数归零后执行close(ch)关闭channel
修改后完整代码
package main import ( "fmt" "net/http" "sync" ) var links = []string{ "https://mcevik.com", "https://stackoverflow.com", "https://www.linkedin.com", "https://github.com", "https://medium.com", "https://kaggle.com", } func getLink(link string, ch chan string, wg *sync.WaitGroup) { // 函数退出时自动标记任务完成 defer wg.Done() if res, err := http.Get(link); err != nil { ch <- err.Error() } else { ch <- fmt.Sprintf("[%d] - %s", res.StatusCode, link) } } func main() { ch := make(chan string, len(links)) var wg sync.WaitGroup for _, link := range links { // 启动goroutine前新增计数 wg.Add(1) go getLink(link, ch, &wg) } // 单独开goroutine等待所有任务完成后关闭channel go func() { wg.Wait() close(ch) }() // 此时channel关闭后range循环会自动退出 for msg := range ch { fmt.Println(msg) } }
注意事项
- 不能在启动所有goroutine后直接在主协程调用
wg.Wait()再关闭channel,否则会发生死锁:主协程等所有goroutine写完,但是goroutine写channel时如果缓冲区满了会阻塞,主协程又没在读channel,就会互相卡住 - 不要提前关闭channel,否则未完成的goroutine向已关闭的channel写入数据会直接触发panic
替代方案(无需关闭channel)
因为明确知道总共有len(links)条返回结果,也可以不用range遍历,直接循环固定次数读取channel,也能正常退出,写法更精简:
func main() { ch := make(chan string, len(links)) for _, link := range links { go getLink(link, ch) } // 固定读取len(links)次,不需要关闭channel for i := 0; i < len(links); i++ { fmt.Println(<-ch) } }
内容的提问来源于stack exchange,提问作者Mustafa CEVIK
相关产品推荐
相关产品推荐

