【go】context

context:

主要的用处如果用一句话来说,是在于简化在多个go routine传递上下文数据,并将多个groutine之间关联起来。控制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个关键方法:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 

上面三种方法比较类似,均会基于 parent Context 生成一个子 ctx,以及一个 Cancel 方法。
如果调用了cancel 方法,ctx 以及基于 ctx 构造的子 context 都会被取消。
不同点在于 WithCancel 必需要手动调用 cancel 方法,WithDeadline可以设置一个时间点,
WithTimeout 是设置调用的持续时间,到指定时间后,会调用 cancel 做取消操作。

func WithValue(parent Context, key, val interface{}) Context 
  • WithCancel 返回一个cancel函数,调用这个函数则可以主动取消context。
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  
<nil>  
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("执行任务超时~")
    }
}

发表评论

您的电子邮箱地址不会被公开。