31

golang context机制

 4 years ago
source link: https://studygolang.com/articles/25784
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么是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...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK