面试题答案
一键面试父Context和子Context之间的关系
在Go中,当创建子Context时,它会继承父Context的截止时间(如果有)、取消信号等属性。子Context依赖于父Context,父Context一旦取消或过期,所有相关的子Context也会被取消。可以将这种关系看作是一种树形结构,父Context是根节点,子Context是子节点,一个父Context可以有多个子Context。
信号传播
- 取消信号:当父Context被取消(通过调用
cancel
函数,该函数是context.WithCancel
等创建Context时返回的取消函数),会向所有子Context发送取消信号。子Context一旦接收到取消信号,其Done
通道就会被关闭,以此通知相关的Go协程需要进行清理操作并结束。 - 截止时间信号:如果父Context设置了截止时间,子Context会继承这个截止时间(或者有自己更早的截止时间)。当截止时间到达,父Context会被取消,进而导致所有子Context被取消。
并发编程中使用嵌套Context可能遇到的问题及解决方案
- 问题
- 资源泄漏:如果子Context没有正确处理取消信号,即使父Context取消了,相关的Go协程可能不会停止,继续占用资源。
- 逻辑错误:错误地使用嵌套Context,例如在不恰当的时机取消父Context,可能导致子任务没有完成预期工作。
- 性能问题:过多的嵌套Context可能增加系统开销,因为每个Context都需要维护一些状态和通道。
- 解决方案
- 资源泄漏:在每个基于Context的Go协程中,及时监听
Done
通道。一旦Done
通道关闭,立即进行清理操作(如关闭文件、数据库连接等)并返回。例如:
- 资源泄漏:在每个基于Context的Go协程中,及时监听
func worker(ctx context.Context) {
select {
case <-ctx.Done():
// 清理资源
return
// 其他业务逻辑
}
}
- **逻辑错误**:仔细设计Context的生命周期和取消逻辑。确保父Context的取消不会过早中断子任务,除非这是预期的行为。在需要等待子任务完成的情况下,可以使用`WaitGroup`来同步。例如:
func main() {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
worker(ctx)
}()
// 执行一些操作
cancel()
wg.Wait()
}
- **性能问题**:尽量减少不必要的Context嵌套。只有在确实需要独立控制取消或截止时间的情况下才创建新的子Context。同时,复用已有的Context可以减少资源开销。