context

context:

主要的用处如果用一句话来说,是在于简化在多个go routine传递上下文数据,控制goroutine的生命周期。

应用场景:

1.官方http包使用context传递请求的上下文数据

2.gRpc使用context来终止某个请求产生的routine树

3.关联任务的取消

…….

A Context carries a deadline, a cancellation signal, and other values across

基本操作:

import (
    "context"
    )
    

根Context:通过context.Background() 创建

子Context:context.WithCancel(parentContext) 创建

ctx,cancel := context.WithCancel(context.Backgroup())

ctx可以传入到接下来的子任务,cancel可以取消当前Context,

当前Context被取消,基于他的子context都会被取消

接受取消通知  <- ctx.Done()

context.Background(),创建一个 根Context实例 =》 emptyCtx

func Background() Context {
    return background
}

background = new(emptyCtx)
type Context interface {

//返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间
    Deadline() (deadline time.Time, ok bool)

//返回一个关闭的channel (Done returns a channel that's closed )当timeout或者调用cancel方法时,将会触发Done
    Done() <-chan struct{}

//context被取消的原因
    Err() error


 //context实现共享数据存储的地方,是协程安全的
    Value(key interface{}) interface{}
}

context库中,有4个关键方法:

  • WithCancel 返回一个cancel函数,调用这个函数则可以主动停止goroutine。
func main() {
    root := context.Background()
    //ctx1, cancel := context.WithDeadline(root, time.Now().Add(6 * time.Second))
    ctx1, cancel := context.WithCancel(root)
    ctx2 := context.WithValue(ctx1, "key2", "value2")
    go watch(ctx2)
    time.Sleep(10 * time.Second)
    fmt.Println("通知监控停止")
    cancel()
    time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
    for {
        select {
        case <- ctx.Done():
            fmt.Println(ctx.Value("key2"), "监控退出了。")
            return
        default:
            fmt.Println(ctx.Value("key2"), "go rountine 监控中。。。")
            time.Sleep(2 * time.Second)
        }
    }
}

输出:

value2 go rountine 监控中。。。
value2 go rountine 监控中。。。
value2 go rountine 监控中。。。
value2 go rountine 监控中。。。
value2 go rountine 监控中。。。
通知监控停止
value2 监控退出了。
  • WithValue WithValue可以设置一个key/value的键值对,可以在下游任何一个嵌套的context中通过key获取value。
func main() {
    root := context.Background()
    ctx1 := context.WithValue(root, "key1", "value1")
    ctx2 := context.WithValue(ctx1, "key2", "value2")
    fmt.Println(ctx1.Value("key1"))
    fmt.Println(ctx1.Value("key11"))
    fmt.Println(ctx2.Value("key2"))
}

输出:
value1  
  
value2
  • WithTimeout 函数可以设置一个time.Duration,到了这个时间则会cancel这个context。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
    return WithDeadline(parent, time.Now().Add(timeout))
}


shutDown := func(interface{}) {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        if e := httpSrv.Shutdown(ctx); e != nil {
            log.Errorf("cronsun node pprof, metrics http server shutdown failed, error: %s", e.Error())
        } else {
            log.Errorf("cronsun node pprof, metrics http server shutdown success~~")
        }
    }
  • WithDeadline WithDeadline函数跟WithTimeout很相近,只是WithDeadline设置的是一个时间点。
func main() {
    d := time.Now().Add(500 * time.Millisecond) //500 ms
    ctx, cancel := context.WithDeadline(context.Background(), d)

    defer cancel()
    c := make(chan bool)
    //模拟耗时代码
    go func() {
        time.Sleep(400 * time.Millisecond) //修改数值大于 500
        c <- true                          //返回
    }()
    select {
    case flag := <-c: //从chan 获取值
        if flag {
            fmt.Println("执行任务成功")
        } else {
            fmt.Println("执行任务失败")
        }
    case <-ctx.Done(): //是ctx的通道先返回的数据
        fmt.Println("执行任务超时~")
    }
}

欢迎关注我们的微信公众号,每天学习Go知识