什么是Context
Context通常被译作上下文,是一个比较抽象的概念。一般理解为程序单元的一个运行状态、现场、快照,而翻译中上下文又很好地诠释了其本质。
每个Goroutine在执行前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络变成下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的Goroutine来获取数据和执行程序逻辑,即一个请求Request,会在多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。
context包
Go的标准库[golang.org/x/net/context]
context常用的使用姿势:
1.web编程中一个请求对应多个Goroutine之间的数据交互
2.超时控制
3.上下文控制
context的底层结构
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
- Deadline 返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间。
- Done 当Context被取消或者超时的时候返回一个close的channel,告诉给context相关的函数要停止当前工作,然后返回了
- Value context实现共享数据存储的地方,是协程安全的
库里提供了4个Context实现
# 完全空的Context,实现的函数也都是返回nil,仅仅只是实现了Context的接口
type emptyCtx int
# 继承自Context,同时也实现了canceler接口
type cancelCtx struct {
Context
mu sync.Mutex
done chan struct{}
children map[canceler]struct{}
err error
}
# 继承自**cancelCtx**,增加了timeout机制
type timerCtx struct {
cancelCtx
timer \*time.Timer // Under cancelCtx.mu.
deadline time.Time
}
# 存储键值对的数据
type valueCtx struct {
Context
key, val interface{}
}
context的创建
为了更方便的创建Context,包里面定义了Background 来作为所有Context的根,它是一个emptyCtx的实例。
var (
background = new(emptyCtx)
todo = new(emptyCtx) //
)
func Background() Context {
return background
}
你可以认为所有的Context是树的结构,Background是树的根,当任一Context被取消的时候,那么继承它的Context 都将被回收。
context实战
- WidhCancel
实现源码:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
实战场景:
执行一段代码,控制执行到某个度的时候,整个程序结束。
吃汉堡比赛,奥特曼每秒吃0-5个,计算吃到10的用时
实战代码
func main() {
ctx, cancel := context.WithCancel(context.Background())
eatNum := chiHanBao(ctx)
for n := range eatNum {
if n >= 10 {
cancel()
break
}
}
fmt.Println("正在统计结果。。。")
time.Sleep(1 * time.Second)
}
func chiHanBao(ctx context.Context) <-chan int {
c := make(chan int)
// 个数
n := 0
// 时间
t := 0
go func() {
for {
//time.Sleep(time.Second)
select {
case <-ctx.Done():
fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n)
return
case c <- n:
incr := rand.Intn(5)
n += incr
if n >= 10 {
n = 10
}
t++
fmt.Printf("我吃了 %d 个汉堡\n", n)
}
}
}()
return c
}
输出
我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
正在统计结果。。。
耗时 6 秒,吃了 10 个汉堡
WithDeadline & WithTimeout
实现源码
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
实战场景:
执行一段代码,控制执行到某个时间的时候,整个程序结束。
吃汉堡比赛,奥特曼每秒吃0-5个,用时10秒,可以吃多少个
实战代码:
func main(){
// ctx, cancel := context.WidhDeadline(context.Background(), time.Now().Add(10))
ctx, cancel := contet.WidhTimeout(context.Background(), 10*time.Second)
chiHanBao(ctx)
defer cancel()
}
func chiHanBao(ctx context.Context) {
n := 0
for {
select {
case <- ctx.Done():
fmt.Println("stop \n")
return
default:
incr := rand.Intn(5)
n += incr
fmt.Printf("我吃了 %d 个汉堡\n", n)
}
time.Sleep(time.Second)
}
}
输出:
我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 14 个汉堡
我吃了 14 个汉堡
stop
- WidthValue
实现源码:
func WidhValue(parent Context, key, value interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() { //必须是可比较类型
panic("key is not comparable")
}
return &valueCtx{parent, key, value}
}
实战场景:
携带关键信息,为全链路提供线索,比如接入elk等系统,需要来一个trace_id,那WithValue就非常适合做这个事。
实战代码:
func main() {
ctx := context.WithValue(context.Background(), "trace_id", "88888888")
// 携带session到后面的程序中去
ctx = context.WithValue(ctx, "session", 1)
process(ctx)
}
func process(ctx context.Context) {
session, ok := ctx.Value("session").(int)
fmt.Println(ok)
if !ok {
fmt.Println("something wrong")
return
}
if session != 1 {
fmt.Println("session 未通过")
return
}
traceID := ctx.Value("trace_id").(string)
fmt.Println("traceID:", traceID, "-session:", session)
}
输出:
traceID: 88888888 -session: 1
参考
https://blog.csdn.net/u011957...
https://www.cnblogs.com/zhang...
有疑问加站长微信联系(非本文作者)