上会说到 context.Done()可以通知到多个 go 程的核心代码,下面会对 context 的 API 作个简单介绍
type Context interface {
// 返回 deadline 时间,如果没有设置 ok 为 false,设置为 true
Deadline() (deadline time.Time, ok bool)
// 如果 context 被取消 case ctx.Done()会立马返回
Done() <-chan struct{}
// 如果 Done 没有被关闭,返回 nil
// 如果 Done 关闭, 会返回错误, 错误原因如下
// context 被取消
// 超时,context 超过截止时间
// Err() 返回错误,对 Err 的连续调用返回同样的错误
Err() error
// 从 context 取出设置的 k/v 变量
Value(key interface{}) interface{}
}
Deadline 函数绑定的 context 如果是 WithTimeout 和 WithDeadline 生成的,ok 变量为 true。如果是 WithCancel 生成的为 false。这点有绕。可以看下面输出
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 0
ctx, _ := context.WithCancel(context.Background())
fmt.Println(ctx.Deadline())
// 1
ctx, _ = context.WithDeadline(context.Background(), time.Now().Add(-time.Second*60))
fmt.Println(ctx.Deadline())
// 2
// 2 和 1 效果一样,只是设置时间方式不一样
ctx, _ = context.WithTimeout(context.Background(), -time.Second*60)
fmt.Println(ctx.Deadline())
// 3
ctx, _ = context.WithDeadline(context.Background(), time.Now().Add(time.Second*60))
fmt.Println(ctx.Deadline())
// 4
// 4 和 3 效果一样,只是设置时间方式不一样
ctx, _ = context.WithDeadline(context.Background(), time.Now().Add(time.Second*60))
fmt.Println(ctx.Deadline())
}
/*输出
0001-01-01 00:00:00 +0000 UTC false
2019-09-26 08:58:43.881587978 +0800 CST m=-59.999921123 true
2019-09-26 08:58:43.881653923 +0800 CST m=-59.999855199 true
2019-09-26 09:00:43.881670028 +0800 CST m=+60.000160905 true
*/
context 控制退出的核心函数。标准库引入 context 虽然褒贬不一,从个人角度来讲觉得挺好,让 case 业务的 chan 都是可取消的,统一了风格。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
select {
case <-ctx.Done():
// case 业务 chan
}
如果 context 被取消,Err 返回错误,多次调用结果一样
package main
import (
"context"
"fmt"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
fmt.Println(ctx.Err())
cancel()
fmt.Println(ctx.Err())
}
/*
输出
<nil>
context canceled
*/
WithValue 可以向 ctx 里面带一些值,类似 cookie 的感觉。由于不能修改原始 context,只能通过派生一个新的 ctx 携带值。 需要注意的是,context 常见用法是一个 head ctx,派生 n 多的子 ctx,为了避免全局空间污染,都会用自定义类型包装下 key 值
package main
import (
"context"
"fmt"
)
type testKey string
func main() {
// 原始 ctx
ctx, _ := context.WithCancel(context.Background())
// 派生一个新的 ctx, 里面包含一个 k,v 数据结构
ctx = context.WithValue(ctx, testKey("mykey"), "xxxxxxxxxxx")
f := func(ctx context.Context, k interface{}) {
if v := ctx.Value(k); v != nil {
fmt.Printf("value = %s\n", v)
return
}
fmt.Printf("not found value, key = %s\n", k)
}
f(ctx, testKey("mykey"))
f(ctx, "mykey")
}