什么是context
Go语言的Context是一个在并发环境中传递请求范围的值、控制请求的生命周期以及取消请求的机制。它是Go标准库中的一个重要组件,用于解决并发程序中的一些常见问题。
解决了什么问题
-
请求范围的值传递:在一个请求处理链中的不同函数、方法或Goroutine之间传递请求相关的值,例如请求ID、用户身份验证信息等。通过Context,可以避免显式地传递参数,简化函数签名和函数调用。
-
请求的生命周期控制:在并发处理中,当一个请求被处理时,可能会创建多个Goroutine来执行各种任务。Context提供了一种机制来控制这些Goroutine的生命周期,确保它们在适当的时候退出或停止。
-
请求的取消信号:有时需要提前取消一个请求的处理,例如处理超时、用户取消请求等情况。Context提供了一种机制来发送取消信号,通知相关的Goroutine停止处理请求,并释放相关资源。
总结起来,Go的Context通过与goroutine和channel的配合使用,提供了一种简洁而可靠的机制来传递请求范围的值、控制请求的生命周期和取消请求。它解决了并发程序中的一些常见问题,使得编写并发安全和可控制的代码变得更加容易。
实现原理
Context的实现原理主要依赖于Go语言中的两个特性:goroutine和channel。
每个Context实例都与一个goroutine关联,并且可以通过goroutine的调用链传递给其他goroutine。这种关联可以通过Context的派生(使用context.WithCancel
、context.WithDeadline
等函数)来实现。当一个Context被取消或到达截止时间时,与之关联的goroutine会收到一个取消信号。
Context的取消信号是通过一个内置的Done
通道实现的。当Context被取消时,该通道会关闭,相关的goroutine可以通过监视这个通道来接收取消信号。一旦接收到取消信号,相关的goroutine应该停止处理请求,并释放相关资源。
Context还提供了一个Value
方法,用于在不同goroutine之间传递请求范围的键值对。这个方法使用了goroutine安全的数据结构来存储键值对,并且可以在整个调用链中进行传递和访问。
Context接口
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
实战
Deadline
超时cancel
package main
import (
"context"
"fmt"
"time"
)
func main() {
now := time.Now()
// 创建5秒后超时时间的context
// 5秒之后内部自动执行cancel方法
ctx, cancel := context.WithDeadline(context.Background(), now.Add(time.Second*5))
// 启动goroutine调用服务
go callService(ctx)
time.Sleep(10 * time.Second)
// 主动调用cancel
cancel()
select {
case <-ctx.Done():
fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
}
}
// callService 模拟耗时任务
func callService(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
return
default:
fmt.Println("继续执行子服务逻辑,time=", time.Now())
time.Sleep(1 * time.Second)
}
}
}
超时之前cancel
package main
import (
"context"
"fmt"
"time"
)
func main() {
now := time.Now()
// 创建5秒后超时时间的context
// 5秒之后内部自动执行cancel方法
ctx, cancel := context.WithDeadline(context.Background(), now.Add(time.Second*10))
// 启动goroutine调用服务
go callService(ctx)
time.Sleep(5 * time.Second)
// 主动调用cancel
cancel()
select {
case <-ctx.Done():
fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
}
}
// callService 模拟耗时任务
func callService(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
return
default:
fmt.Println("继续执行子服务逻辑,time=", time.Now())
time.Sleep(1 * time.Second)
}
}
}
Timeout
超时cancel
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建5秒后超时时间的context
// 5秒之后内部自动执行cancel方法
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
// 启动goroutine调用服务
go callService(ctx)
time.Sleep(10 * time.Second)
// 主动调用cancel,ctx在这之前已经调用cancel
cancel()
}
// callService 模拟耗时任务
func callService(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到context的canceled信号,退出服务,time=", time.Now())
return
default:
fmt.Println("继续执行服务逻辑,time=", time.Now())
time.Sleep(1 * time.Second)
}
}
}
在超时之前cancel
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建5秒后超时时间的context
// 5秒之后内部自动执行cancel方法
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
// 启动goroutine调用服务
go callService(ctx)
time.Sleep(5 * time.Second)
// 主动调用cancel
cancel()
select {
case <-ctx.Done():
fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
}
}
// callService 模拟耗时任务
func callService(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
return
default:
fmt.Println("继续执行子服务逻辑,time=", time.Now())
time.Sleep(1 * time.Second)
}
}
}
监听系统信号
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// 创建5秒后超时时间的context
// 5秒之后内部自动执行cancel方法
ctx, cancel := context.WithCancel(context.Background())
// 启动goroutine调用服务
go callService(ctx)
// 启动goroutine监听系统终止信号
go func() {
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
select {
case <-sigs:
cancel()
}
}()
// 阻塞,直到context canceled
select {
case <-ctx.Done():
fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
}
}
// callService 模拟耗时任务
func callService(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
return
default:
fmt.Println("继续执行子服务逻辑,time=", time.Now())
time.Sleep(1 * time.Second)
}
}
}
http客户端终止请求
package main
import (
"fmt"
"net/http"
"time"
)
func hello(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
fmt.Println("server: hello handler started")
defer fmt.Println("server: hello handler ended")
// 阻塞。直到任何一个case满足结束阻塞
select {
case <-time.After(10 * time.Second):
// 10秒之后满足
fmt.Fprintf(w, "hello\n")
case <-ctx.Done():
// http客户端终止请求
// curl命令调用接口时,按control+c终止API请求
err := ctx.Err()
fmt.Println("server:", err)
internalError := http.StatusInternalServerError
http.Error(w, err.Error(), internalError)
}
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}