当错误通道传入错误时Goroutine未正常返回,导致错误消息未打印
嘿,这个问题我之前做类似的进程监控工具时也踩过坑!咱们来一步步排查可能的原因,大概率是这几个细节没处理好:
1. Goroutine发送错误后没正确触发WaitGroup的Done
你有没有确保每个监控goroutine在遇到错误、发送完错误信息后,都调用了wg.Done()?很多人会在正常循环结束时处理,但容易忘了在错误分支里兜底,导致WaitGroup一直处于等待状态,主goroutine卡着没法去读取错误通道。
举个典型的错误写法:
func monitor(pid int, wg *sync.WaitGroup, errChan chan<- error) { wg.Add(1) for { _, err := readProc(pid) if err != nil { errChan <- err // 这里漏了wg.Done()! return } time.Sleep(time.Second) } }
正确的做法是用defer wg.Done(),不管goroutine是正常结束还是报错退出,都会自动触发:
func monitor(pid int, wg *sync.WaitGroup, errChan chan<- error) { defer wg.Done() // 关键!放在函数最开头 for { _, err := readProc(pid) if err != nil { errChan <- fmt.Errorf("进程%d监控失败:%w", pid, err) return } time.Sleep(time.Second) } }
2. 错误通道的缓冲容量不足导致发送阻塞
如果你的错误通道是无缓冲的(make(chan error)),当goroutine尝试发送错误时,会一直阻塞直到主goroutine接收这个错误。但如果主goroutine此时在等wg.Wait(),那就会形成死锁——goroutine卡着发不出错误,主goroutine卡着等所有goroutine结束,自然看不到错误信息。
解决办法很简单:给错误通道设置足够的缓冲容量,比如你有N个监控goroutine,就设为make(chan error, N),这样即使主goroutine还没准备好接收,goroutine也能把错误发进去然后正常退出。
3. 主goroutine的等待逻辑顺序错了
很多人的写法是先等所有goroutine完成(wg.Wait()),再去读取错误通道,但如果某个goroutine因为发送错误阻塞了,wg.Wait()永远不会完成,主goroutine根本没机会去读错误。
正确的做法是同时等待错误通道和WaitGroup完成,用select来实现:
func main() { targetPid := 1234 errChan := make(chan error, 1) var wg sync.WaitGroup wg.Add(1) go monitor(targetPid, &wg, errChan) // 用select同时监听错误和所有goroutine完成的信号 select { case err := <-errChan: fmt.Printf("监控出错:%v\n", err) // 如果有多个goroutine,这里可以用context触发全局退出 case <-func() chan struct{} { done := make(chan struct{}) go func() { wg.Wait() close(done) }() return done }(): fmt.Println("所有监控任务正常完成") } close(errChan) }
这里我们把wg.Wait()放到一个单独的goroutine里,完成后关闭一个信号通道,主goroutine通过select同时监听错误和这个信号,哪边先触发就处理哪边,不会卡死。
额外提醒:用Context控制goroutine生命周期
如果你的监控有多个goroutine,最好用context.WithCancel()来管理它们的生命周期——一旦某个goroutine报错,主goroutine可以调用cancel()让其他goroutine也及时退出,避免资源浪费。
比如修改monitor函数:
func monitor(ctx context.Context, pid int, wg *sync.WaitGroup, errChan chan<- error) { defer wg.Done() ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return // 收到取消信号,立刻退出 case <-ticker.C: _, err := readProc(pid) if err != nil { errChan <- fmt.Errorf("进程%d监控失败:%w", pid, err) return } } } }
内容来源于stack exchange

