介绍

Context 标准库是 go1.7 版本之后引入的,中文叫 goroutine 的上下文。包含 goroutine 的运行状态、环境等信息。

主要作用是:goroutine 之间传递上下文信息,如

  1. 取消信号
  2. 超时信号
  3. 截止时间信
  4. Key-value 值。

作用

传递 key-value 值

主要时候通过 context 包的 withvalue 方法传入 parent 上下文对象和 key-value,然后通过 value 方法获取到 key 的值。

如果是根的上下文,它的 parent 上下文为空,也就是传入 context.Background()

package main

import (
  "context"
 "fmt"
 "time"
)

func g(ctx context.Context) {
  // 传进来的ctx不可变
   fmt.Println(ctx.Value("begin"))
   fmt.Println("你是猪")
    // ctx 作为g2协成的parent
    go g2(context.WithValue(ctx, "movie", "正义联盟"))
}

func g2(ctx context.Context) {
   fmt.Println(ctx.Value("movie"))
   fmt.Println("电影很好看")
}

func main() {
    // 根的上下文,所有父的上下文是空的,也就是context.Background()
 ctx := context.WithValue(context.Background(), "begin", "从台词里看到一句话:")
   go g(ctx)
   time.Sleep(time.Second)
}

context 无限嵌套,可以获取父上下文的值吗?

是可以的,如下所示

ctx := context.Background()
ctx = context.WithValue(ctx, "one", 1)
ctx = context.WithValue(ctx, "two", 2)
// 会递归获取到父上下文的值的
fmt.Println(ctx.Value("one")) // 输出1

取消信号

主要是通过 WithCancel 方法来实现,该方法会返回两个值,一个是上下文对象,另外一个是函数,可以调用传递取消信号给各个协程,每个协程里可以通过 select 来监听信号,从而结束协程

package main

import (
    "context"
 "log"
 "time"
)

func g1(ctx context.Context) {
 go g2(ctx)
  select {
    // 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
    case <-ctx.Done():
      log.Println("g1 被取消了")
        // cancel() 不需要cancle
       return
  }
}

func g2(ctx context.Context) {
    ctx2, cancel := context.WithCancel(ctx)
 go g3(ctx2)
 select {
    // 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
    case <-ctx.Done():
      cancel() // ctx2传递cancel信号
      log.Println("g2 被取消了")
        return
  }
}

func g3(ctx context.Context) {
    ctx3, _ := context.WithCancel(ctx)
  go g4(ctx3) // 不传递cancel信号,g4也会被取消,不知为啥
 select {
    // 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
    case <-ctx.Done():
      log.Println("g3 被取消了")
        return
  }
}

func g4(ctx context.Context) {
    select {
    // 只要能够从channel里取值出来,就代表外部传递了cancel信号了,具体是什么值不重要
    case <-ctx.Done():
      log.Println("g4 被取消了")
        return
  }
}

func main() {
 // 返回两个值,cancel是个函数,用于传递信号
  ctx, cancel := context.WithCancel(context.Background())
 go g1(ctx)

  count := 1
  for {
       if count >= 3 {
         // 外部手动执行cancel,把信号通过channel的形式传递给协程g
           // g1、g2、g3、g4 都会被取消
            cancel()
        }

       time.Sleep(time.Second)
     count++
 }
}

超时取消

通过 WithTimeout 方法来设置超时时间,如果协程执行的时间超时了,会自动传递 cancel 信号给协成,不需要自己手动调用 cancel 方法。

下例是手动取消和超时取消混合使用:

package main

import (
    "context"
 "log"
 "time"
)

func g1(ctx context.Context) {
 done := make(chan struct{}, 1)
  go func() {
     // 这边延迟了1s,所以会超时取消
      time.Sleep(time.Second * 1)
     done <- struct{}{}
  }()
 select {
    case <-ctx.Done():
      log.Println("g1 被取消或超时了")
     // cancel() 不需要cancle
       return
  case <-done:
        log.Println("g1 正常完成")
        return
  }
}

func main() {
 // 500ms超时
  ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
  go g1(ctx)

  count := 1
  for {
       if count >= 3 {
         log.Println("手动取消")
           cancel()
            count = 0
       }

       time.Sleep(time.Second)
     if count > 0 {
          count++
     }
   }
}
最后修改:2023 年 12 月 30 日
如果觉得我的文章对你有用,请随意赞赏