kevinyan815 / gocookbook

go cook book

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Context 上下文

kevinyan815 opened this issue · comments

Context 与 Go 语言中的并发编程有着比较密切的关系,在其他语言中我们很难见到类似 Context 的东西,它不仅能够用来设置截止日期、同步『信号』还能用来传递请求相关的值。

Go 语言里每一个并发的执行单元叫做 goroutine,当一个用Go语言编写的程序启动时,main 函数在一个单独的 goroutine 中运行。main 函数返回时,所有的goroutine都会被直接打断,程序退出。除此之外如果想通过编程的方法让一个goroutine 中断其他 goroutine 的执行,只能是通过在多个 goroutine 间用 context 上下文对象同步取消信号的方式来实现。

Context 其实是 Go 语言 context 包对外暴露的接口,

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

常用下面两个方法

  • Done 方法需要返回一个 Channel,多次调用 Done 方法会返回同一个 Channel,这个 Channel 会在工作完成或者上下文被取消之后close掉,通过关闭Channel的方式,实现通知使用该 Context 的所有goroutine。

  • Err 方法会返回当前 Context 结束的原因,它只在 Done 返回的 Channel 被关闭时才会返回非空的值;

    如果当前 Context 被取消就会返回 Canceled 错误;

    如果当前 Context 超时就会返回 DeadlineExceeded 错误;

Context一般有下面两种用法。

用法一:主动发起取消通知

使用context.WithCancel 创建一个支持取消功能的上下文。

func operation1(ctx context.Context) error {
        // 假设这个操作会因为某种原因失败
        // 使用time.Sleep来模拟一个资源密集型操作
    time.Sleep(100 * time.Millisecond)
    return errors.New("failed")
}
 
func operation2(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("canceled operation2")
            return
        // 注意:省略default分支 整个goroutine 会被阻塞住
        case default :
        }
 
 
        // 可以选择在default分支或者是这里执行业务逻辑
        time.Sleep(10 * time.Millisecond)
    }
}
 
func main() {
    // 新建一个上下文
    ctx := context.Background()
        // 在初始上下文的基础上创建一个有取消功能的上下文
    ctx, cancel := context.WithCancel(ctx)
        // 在不同的goroutine中运行operation2
    go func() {
      operation2(ctx)
    }()
   
  err := operation1(ctx)
        // 如果这个操作返回错误,取消所有使用相同上下文的 goroutine 的执行
    if err != nil {
        cancel()
    }
}

用法二:基于时间到期后自动取消通知

// 这个上下文将会在3秒后被取消
// 如果需要在到期前就取消可以像前面的例子那样使用cancel函数
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
 
// 上下文将在2009-11-10 23:00:00被取消
ctx, cancel := context.WithDeadline(ctx, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))

worker goroutine 内部要跟上面的例子一样,通过 select case <- Done() 接收通道传递过来的取消信号。唯一的
区别是这两种Context是到期自动同步信号,不需要在 main goroutine 内主动触发

如果想在到期前提前让 worker goroutine 结束执行,调用创建Context时返回的 cancel 函数。

更多 Context 的应用示例:https://github.com/kevinyan815/gocookbook/tree/master/codes/context_demo