编程后端Go开发者

Go中内置的context包是如何工作的?它用于什么,如何正确创建和结束上下文?在使用时常见的错误有哪些?

用 Hintsage AI 助手通过面试

答案

context包是管理生命周期(取消、超时)和在代码部分之间传递元数据的标准,尤其是在与goroutine和外部调用(HTTP、DB)一起工作时。

创建:

  • context.Background() — 根上下文
  • context.WithCancel(parent) — 带取消的新上下文
  • context.WithTimeout(parent, duration) — 带超时的新上下文
  • context.WithValue(parent, key, value) — 带数据的新上下文

结束上下文对于正确释放资源非常重要。示例:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() // 之后在goroutine/HTTP请求中使用ctx

典型模式是将上下文作为函数的第一个参数,示例:

func process(ctx context.Context) error { ... }

挂羊头卖狗肉的问题

什么时候可以用context.TODO()和context.Background()代替真实的上下文,两者有什么区别?

许多人将context.Background()用作占位符。但:

  • context.Background()是树的根节点,仅在main()和测试中使用,在初始化“上下文树”时使用。
  • context.TODO()在不清楚应使用什么上下文时使用 — 用于重构时的临时“填补漏洞”。在生产代码中,TODO是不可接受的:必须清楚知道传递的内容和去向。

由于对主题细节的不了解而导致的实际错误示例


故事

在微服务中,创建context.WithTimeout()后忘记调用cancel() — 请求被阻塞,结束的goroutine在运行时留下“悬挂”的计时器,导致内存泄漏。


故事

在处理程序中传递context.Background()代替请求中传来的上下文,丢失了整个取消链和trace-id,导致超时限制失效,请求取消未生效。


故事

通过context.WithValue()开发人员传递数据,但键是字符串。结果,由于来自不同包的键可能发生冲突,出现了意想不到的错误(“键已占用”)。正确的做法是使用唯一类型作为键。