在 Go 语言中,context 是一个用于传递上下文信息、控制协程(goroutine)生命周期以及在协程之间传递取消信号的机制。它在并发编程中非常有用,可以用来优雅地管理协程的生命周期、传递超时和取消信号,以及避免资源泄漏。
常用的生成顶层context的方法:
- context.Background(): 返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。一般用在主函数、初始化、测试以及创建根 Context 的时候。
- context.TODO(): 返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。当你不清楚是否该用 Context,或者目前还不知道要传递一些什么上下文信息的时候,就可以使用这个方法。
context 包含了一些方法和类型,用于创建和操作上下文。以下是一些 context 的主要作用:
- 传递上下文信息:
context可以用于在协程中传递上下文信息,比如请求相关的信息、用户认证等。这可以避免在函数参数中一层层地传递这些信息。 - 控制生命周期: 通过
context,你可以取消协程的执行。当一个context被取消时,所有基于该context的协程都会收到取消信号,从而可以优雅地终止协程的执行。 - 传递超时和截止时间: 你可以使用
context来设置协程的超时和截止时间,当超过指定的时间时,context会被取消,从而取消相关的协程。
示例代码
传递上下文信息
package main
import (
"context"
"fmt"
"time"
)
// 上下文信息传递
func authenticateRequest(ctx context.Context, userID int) {
// 将用户ID存入上下文中
ctx = context.WithValue(ctx, "userID", userID)
fmt.Printf("User %d authenticated\n", userID)
go processRequest(ctx, 1)
go processRequest(ctx, 2)
}
func processRequest(ctx context.Context, requestID int) {
// 从上下文中获取请求相关信息
userID := ctx.Value("userID").(int)
fmt.Printf("Processing request %d for user %d\n", requestID, userID)
select {
case <-time.After(2 * time.Second):
fmt.Printf("Request %d processed\n", requestID)
case <-ctx.Done():
fmt.Printf("Request %d canceled due to context\n", requestID)
}
}
func main() {
var userID int
userID = 123
//userID = 1 触发ctx.Done的userID
// 创建一个可取消上下文
parentCtx, cancelParent := context.WithCancel(context.Background())
// 调用身份验证函数,传递上下文
authenticateRequest(parentCtx, userID)
if userID == 1 {
cancelParent() // 触发ctx.Done
}
// 等待一些时间,模拟主程序运行
time.Sleep(3 * time.Second)
}
控制生命周期
package main
import (
"context"
"fmt"
"time"
)
// context 控制生命周期
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d: canceled\n", id)
return
default:
fmt.Printf("Worker %d: working...\n", id)
time.Sleep(1 * time.Second)
}
}
}
func main() {
// 创建一个可以取消的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在主函数退出时取消协程
// 启动两个协程
go worker(ctx, 1)
go worker(ctx, 2)
// 等待一段时间后取消协程的执行
time.Sleep(3 * time.Second)
fmt.Println("Main function: canceling workers...")
cancel() // 取消协程的执行
time.Sleep(1 * time.Second) // 给协程一点时间完成清理
fmt.Println("Main function: done")
}
传递超时和截止时间
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建一个具有 5 秒超时的 context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // 确保在主函数退出时取消 context
// 启动一个协程来执行耗时操作
go func(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("Long operation completed successfully.")
case <-ctx.Done():
fmt.Println("Long operation canceled due to timeout.")
}
}(ctx)
// 等待一段时间,确保协程有足够的时间执行
time.Sleep(6 * time.Second)
fmt.Println("Main function: done")
}