Go中的context

733 阅读3分钟

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

context

context为Go中的一个标准库, 专门用来处理多个协程之间的控制问题, 比如协程的取消, 协程运行截止时间, 协程运行的超时时间, 协程之间的数据传输等.

当一个协程被退出时, 我们可能需要所有的由它开启的协程全部退出, 释放这些已经不再使用的资源, 就需要用到context

 type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
 }

Deadline() 返回当前context截止时间, 如果有返回true, 没有返回false

Done() 当context取消时, 会调用该方法, 向通道写入数据, 由此可以判断是否要退出当前协程的执行

Err() 返回当前context执行状态的信息, context被取消时Err()有有数据

Value() 用于不同协程之间的数据传输

context使用规则

  1. context要作为函数的第一个参数使用, 不要试图将它放入结构体中
  2. 当方法或函数需要一个context时, 不要传入nil, 可以传入context.TODO
  3. context可以传入到不同的协程中, 并且在多个协程中是安全的
  4. 当context传入到子协程中时, 需要在子协程中监控conext.Done()返回的通道, 当收到通知时, 停止当前子协程的运行, 释放资源并返回到上层

sync.WaitGroup方式

全局变量方式

 var (
   wg sync.WaitGroup
   exit bool
 )
 ​
 // 采用全局变量的方式结束协程
 func globalFunc()  {
    wg.Add(1)
    go globalFunc2()
 ​
    // 当main函数运行5s后, 退出协程f
    time.Sleep(time.Second * 5)
 ​
    // 这时需要将全局变量exit设置true
    // 当协程访问这个变量时, 就知道是该退出的时候了
    exit = true
    wg.Wait()
 }
 ​
 func globalFunc2() {
    // 每隔1s打印do...
    for true {
       fmt.Println("globalFunc2...")
       time.Sleep(time.Second)
       
       // 如果为false表明继续执行
       // 如果为true表明, 启动这个协程的上级协程, 要求它需要退出了
       if exit {
          fmt.Println("globalFunc2 done...")
          break
       }
    }
    wg.Done()
 }

通道方式

 func chanFunc()  {
    wg.Add(1)
    var b = make(chan bool)
    go chanFunc2(b)
 ​
    // 当main函数运行5s后, 退出协程f
    time.Sleep(time.Second * 5)
 ​
    // 向通道写入数据, 表明调用协程要求子协程退出
    b <- true
 ​
    wg.Wait()
 }
 ​
 func chanFunc2(c chan bool) {
    // 每隔1s打印do...
    for true {
       fmt.Println("chanFunc2...")
       time.Sleep(time.Second)
       select {
       // 当能从通道中读取数据时, 表明要求当前协程要退出执行了
       case <- c:
          fmt.Println("chanFunc2 done!")
          wg.Done()
          break
       default:
       }
    }
 }

context.WithCancel的使用

 func ctxCancelFunc()  {
    wg.Add(1)
 ​
    // 使用withCancel来创建一个context实例
    // 它的根content为context.Background()
    ctx, cancelF := context.WithCancel(context.Background())
 ​
    go ctxCancelFunc2(ctx)
 ​
    // 当main函数运行5s后, 通知协程f退出
    time.Sleep(time.Second * 5)
 ​
    // 调用创建ctx时的, cancelF函数来通知子协程退出
    cancelF()
 ​
    wg.Wait()
 }
 ​
 // 子协程
 func ctxCancelFunc2(ctx context.Context)  {
    // 每隔1s打印do...
    for true {
       fmt.Println("ctxCancelFunc2...")
       time.Sleep(time.Second)
       select {
       // 当上级调用cancel方法后, 这里会收到通知
       case <- ctx.Done():
          fmt.Println("ctxCancelFunc2 done...")
          wg.Done()
          break
       default:
       }
    }
 }

context.WithTimeout的使用

 func timeoutFunc()  {
 ​
    // 定义一个时间间隔
    d := time.Second * 5
 ​
    // 使用context.WithTimeout来创建一个context
    // 它的根context为context.Background()
    ctx, cancelF := context.WithTimeout(context.Background(), d)
 ​
    // 虽然到了一定时间会自动调用cancel函数
    // 但是为了保证安全, 在当前协程执行完毕的时候, 还是需要手动调用一下的
    defer cancelF()
 ​
    wg.Add(1)
    
    go timeoutFunc2(ctx)
 ​
    // 当前协程执行时间为10s
    // 但是当context的时间到了之后, 就会取消子协程的执行
    time.Sleep(time.Second * 8)
 }
 ​
 func timeoutFunc2(ctx context.Context)  {
    defer func() {
       fmt.Println("timeoutFunc2 done!")
    }()
    for {
       fmt.Println("timeoutFunc2 ...")
       time.Sleep(time.Second)
       select {
       // 当子协程收到通知后, 退出当前协程的执行
       case <-ctx.Done():
          wg.Done()
          return
       default:
       }
    }
 }

context.WithDeadline的使用

 func deadlineFunc()  {
 ​
    // 定义子协程运行截止时间
    dl := time.Now().Add(time.Second * 5)
 ​
    // 通完context.WithDeadline创建一个context实例
    ctx, cancelF := context.WithDeadline(context.Background(), dl)
 ​
    // 虽然到期自动启用
    // 但是为了保证运行安全, 在当前协程退出时间, 还是需要手动调用一下的
    defer cancelF()
 ​
    wg.Add(1)
    go deadlineFunc2(ctx)
 ​
    // 当前协程支持时间, 大于子协程要退出的时间
    time.Sleep(time.Second * 8)
    
    wg.Wait()
 }
 ​
 func deadlineFunc2(ctx context.Context)  {
    defer func() {
       fmt.Println("deadlineFunc2 done!")
    }()
    for {
       fmt.Println("deadlineFunc2 ...")
       time.Sleep(time.Second)
       select {
       // 当收到context的取消通知后, 退出当前子协程的执行
       case <-ctx.Done():
          wg.Done()
          return
       default:
       }
    }
 }

context.WithValue的使用

 func valueFunc()  {
    // 使用context.WithValue创建一个conext
    // 设置要通过context要传递的键值
    ctx := context.WithValue(context.Background(), "key", "value")
 ​
    go valueFunc2(ctx)
 ​
    time.Sleep(time.Second)
 }
 ​
 func valueFunc2(ctx context.Context)  {
 ​
    // 通过context提供的Value方法来获取数据
    v := ctx.Value("key")
 ​
    fmt.Println("value: ", v)
 }