父goroutine退出后子goroutine未终止的原因是什么?
父Goroutine结束后子Goroutine未退出的原因分析
示例代码
func sleep(d time.Duration, ch chan<- int) { fmt.Println("Sleeping...") time.Sleep(d) fmt.Println("Awake...") ch <- 0 } func parent(ctx context.Context) int { ch := make(chan int, 1) go sleep(3*time.Second, ch) select { case <-ctx.Done(): return 1 case <-ch: return 0 } } func app(wg *sync.WaitGroup) { defer wg.Done() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() res := parent(ctx) fmt.Println(res) } func main() { var wg sync.WaitGroup wg.Add(1) go app(&wg) wg.Wait() time.Sleep(2 * time.Second) }
问题描述
在这个示例中,我设置了一个超时时间为2秒的ctx。子goroutine(sleep函数)需要3秒才能完成执行。2秒后,parent函数因触发<-ctx.Done()分支返回1。但在主线程中我再休眠2秒后,发现子goroutine并未退出,仍在继续执行并打印"Awake..."。
为何父goroutine结束后子goroutine没有退出?
程序输出
Sleeping... 1 Awake...
原因解析
Go语言里,goroutine之间不存在默认的"父子依赖"关系——父goroutine的退出不会主动终止子goroutine。只要子goroutine的代码还没执行完,它就会继续运行直到自身逻辑完成,完全不受创建它的父goroutine状态影响。
具体到你的代码流程:
parent函数启动sleep子goroutine后,进入select语句等待事件;- 2秒后
ctx触发超时,parent函数返回1,随后app函数执行完毕并调用wg.Done(); - 此时
sleep子goroutine还在执行time.Sleep(3*time.Second),它没有任何机制感知到父goroutine已经结束; - 当3秒休眠结束后,子goroutine会继续执行后续代码:打印"Awake...",然后向缓冲通道
ch发送数据——因为ch容量为1,发送操作不会阻塞,子goroutine执行完所有代码后才会退出。
解决方案:让子goroutine响应父goroutine的退出
如果需要子goroutine随父goroutine的退出而终止,需要主动传递取消信号:
- 修改
sleep函数,让它接收context.Context参数,在执行过程中检查取消信号; - 利用
context的取消机制,让子goroutine感知到父goroutine的退出事件。
修改后的sleep函数示例:
func sleep(ctx context.Context, d time.Duration, ch chan<- int) { fmt.Println("Sleeping...") select { case <-ctx.Done(): fmt.Println("Sleep goroutine canceled") return case <-time.After(d): fmt.Println("Awake...") ch <- 0 } }
同时在parent函数中把ctx传递给sleep:
go sleep(ctx, 3*time.Second, ch)
这样当ctx超时后,子goroutine会立即收到取消信号,提前退出,不会再执行后续的打印和发送操作。
内容的提问来源于stack exchange,提问作者Mojtaba Arezoomand
相关产品推荐
相关产品推荐

