关于Context:
在WebServer中,
每个请求都对应一个goroutine
,同时还会启动若干goroutine
去处理后端业务,如数据库
、RPC服务
、身份认证信息
等。
当一个请求被取消或超时
时,所有用来处理该请求的goroutine
都应该迅速退出,然后系统才能释放这些goroutine占用的资源。
Context解析:
-
context包括运行环境、现场和快照等。context 包主要是用来处理多个goroutine 之间共享数据,及多个 goroutine 的管理。
-
Done() 返回一个只能接受数据的channel类型,当该context关闭或者超时时间到了的时候,该channel就会有一个取消信号。
-
Context通常最为函数的第一个参数使用,且不要传递nil(可使用
context.Background
或context.TODO
)。 -
Value() 方法允许 Context 对象携带request作用域的数据,该数据必须是线程安全的。
-
Context的核心是围绕**
取消子协程
**展开的,上下文核心成员是Done() <-chan struct{}
。 -
context.Background()
是emptyCtx
类型,它实现了Context
接口,WithCancel等是责任链模式。 -
context.TODO()
与context.Background()
等价,都是一个emptyCtx
类型。 -
通过向下传递
time.Duition
、time.Time
或cancel回调函数
控制取消的时机。
1. 取消
func main() {
// 传统方式:读变量
cancelByVar()
fmt.Println("==================")
// 传统方式:通信
cancelByChannel()
fmt.Println("==================")
// 推荐:上下文通道
cancelByContext()
fmt.Println("==================")
// 扩展案例
withCancelEx()
}
// 方式一:变量控制取消操作
func cancelByVar() {
var cancel = false
go func() {
for {
if cancel {
break
}
fmt.Println("时间心跳:", time.Now().Second())
time.Sleep(time.Second)
}
}()
time.Sleep(time.Second * 3)
cancel = true
}
// 方式二:channel控制取消操作
func cancelByChannel() {
var cancel = make(chan struct{})
go func(c chan struct{}) {
OVER:
for {
select {
case <-c:
break OVER
default:
}
fmt.Println("时间心跳:", time.Now().Second())
time.Sleep(time.Second)
}
}(cancel)
time.Sleep(time.Second * 3)
cancel <- struct{}{}
}
// 方式三:context手动控制取消
func cancelByContext() {
process := func(ctx context.Context) {
OVER:
for {
fmt.Println("时间心跳:", time.Now().Second())
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 向上报告
break OVER
default:
}
}
}
ctx, cancel := context.WithCancel(context.Background())
go process(ctx)
time.Sleep(time.Second * 3)
cancel() //三秒后通知取消
}
// WithCancel案例
func withCancelEx() {
fetch := func(ctx context.Context) <-chan int {
trans := make(chan int) //单通道必须经过中转
go func() {
OVER:
for {
select {
case <-ctx.Done():
break OVER
// (模拟)发送当前时间秒
case trans <- time.Now().Second():
time.Sleep(time.Second)
}
}
}()
return trans
}
ctx, cancel := context.WithCancel(context.Background())
for n := range fetch(ctx) {
fmt.Println("时间心跳:", n)
if n%5 == 0 {
break
}
}
cancel()
}
2. 超时
func main() {
withTimeout()
}
func withTimeout() {
var wg sync.WaitGroup
wg.Add(1)
process := func(ctx context.Context) {
OVER:
for {
time.Sleep(time.Second)
select {
case er := <-ctx.Done():
fmt.Println(er) //取消的原因
break OVER
default:
fmt.Println("时间心跳:", time.Now().Second())
}
}
wg.Done()
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
go process(ctx)
_ = cancel //cancel是个方法,这也可以手动提前结束
wg.Wait()
}
3. 过期
func main() {
dead := time.Now().Add(3 * time.Second) //截止时间
ctx, cancel := context.WithDeadline(context.Background(), dead)
defer cancel()
// 会员有效期检测
select {
case <-time.After(1 * time.Second):
fmt.Println("欢迎光临,尽情畅玩吧...")
case <-ctx.Done():
fmt.Println("会员失效,请充值...", ctx.Err())
}
fmt.Println("over")
}
4. 传值
func main() {
process := func(ctx context.Context) {
trace_id, ok := ctx.Value("trace_id").(int)
if ok {
fmt.Println("trace_id =", trace_id)
}
session, _ := ctx.Value("session").(string)
fmt.Println("session =", session)
}
// 上下文传值
ctx := context.WithValue(context.Background(), "trace_id", 1001)
ctx = context.WithValue(ctx, "session", "86011WQGJ")
process(ctx)
}