context在Go语言中是一个非常重要的知识点,在使用场景和面试问答中经常被提及到,如果想学习其原理知识,可以参考, 这篇文章只做使用场景总结。
context包为协程间传递信号提供了一些方法,用于管理请求生命周期和控制并发操作,context的使用场景有以下几个:
控制请求的生命周期
在处理HTTP请求时,通常需要确保请求处理过程中能够及时取消、超时或结束。尤其当请求涉及到多个下游服务调用时,若一个服务响应缓慢或失败,必须取消所有其他正在进行的操作。
示例:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
resultChan := make(chan string, 1) // 使用缓冲通道避免 goroutine 泄漏
// 启动 goroutine 执行耗时操作
go func() {
// 模拟耗时操作
select {
case <-time.After(2 * time.Second):
// 检查父 context 是否已经结束
select {
case resultChan <- "operation completed successfully":
log.Println("Operation completed and result sent")
case <-ctx.Done():
log.Println("Operation completed but context was canceled, discarding result")
}
case <-ctx.Done():
log.Println("Operation canceled before completion")
// 不发送结果,因为 context 已取消
}
}()
// 等待结果或取消信号
select {
case <-ctx.Done():
// 请求取消或超时
switch ctx.Err() {
case context.Canceled:
log.Println("Request was canceled by client")
http.Error(w, "request canceled", http.StatusRequestTimeout)
case context.DeadlineExceeded:
log.Println("Request timeout")
http.Error(w, "request timeout", http.StatusGatewayTimeout)
default:
log.Printf("Context error: %v", ctx.Err())
http.Error(w, "request failed", http.StatusInternalServerError)
}
return
case result := <-resultChan:
// 正常返回结果
log.Println("Returning successful result")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintln(w, result)
}
}
处理超时和截止时间
当处理需要网络调用或长时间运行的操作时,设定一个超时时间或截止日期是很重要的。context可以传递一个超时或截止日期,自动取消操作,避免资源浪费。
示例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
fmt.Println(fetchData(ctx))
}
func fetchData(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
ch := make(chan string, 1)
go func() {
//模拟耗时操作
time.Sleep(3 * time.Second)
ch <- "data"
}()
select {
case <-ctx.Done():
fmt.Println("Done")
return "", ctx.Err()
case result := <-ch:
fmt.Println("result")
return result, nil
}
}
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 设置一个具体的截止时间
deadline := time.Now().Add(3 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel() // 重要:确保资源被释放
// 检查截止时间
if dl, ok := ctx.Deadline(); ok {
fmt.Printf("Deadline set to: %v\n", dl.Format("15:04:05.000"))
}
// 等待 context 超时
select {
case <-time.After(5 * time.Second):
fmt.Println("Operation completed")
case <-ctx.Done():
fmt.Printf("Context canceled: %v\n", ctx.Err())
}
}
传递元数据
在微服务架构中,需要在服务之间传递一些与请求相关的元数据,例如认证信息、分布日志ID等,context提供了传递这些信息的方式。
示例:
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "requestID", "12345")
processRequest(ctx)
}
func processRequest(ctx context.Context) {
reqID := ctx.Value("requestID")
fmt.Println("Request ID:", reqID)
}
协同工作
在复杂的并发任务中,不同的协程可能需要相互协作,或需要再特定条件下取消其他协程,context可以用于协同工作,统一管理多个协程的状态。
func main() {
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx, "worker1")
go worker(ctx, "worker2")
time.Sleep(1 * time.Second)
cancel() // 取消所有工作
time.Sleep(1 * time.Second)
}
func worker(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "stopped")
return
default:
fmt.Println(name, "working")
time.Sleep(500 * time.Millisecond)
}
}
}
限制并发数量
在特定场景下,需要限制并发执行的协程数量,避免过度消耗系统资源,context可以与信号量或sync.WaitGroup一起使用来限制并发数量。
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sem := make(chan struct{}, 3) // 限制并发数为3
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
sem <- struct{}{} // 获取信号
defer func() { <-sem }() // 释放信号
worker(ctx, i)
}(i)
}
wg.Wait()
}
func worker(ctx context.Context, id int) {
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)
}
}