关于Golang 并发的两个提问

96 阅读2分钟

请问怎么使用 context.Context package里面的 Done()

context.Context是一个接口,它定义了四个方法:

  • Deadline() (deadline time.Time, ok bool)

  • Done() <-chan struct{}

  • Err() error

  • Value(key interface{}) interface{}

Done()方法返回一个chan struct{},我们可以在这个channel上等待context被cancel的信号。这通常用于协程间通信,告诉其他协程这个context已经结束。举个例子:

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

go func() {
    for {
        select {
        case <-ctx.Done():
            println("context cancelled")
            return
        default:
            // do some work
        }
    }
}()

// cancel context after 1 second
time.AfterFunc(1*time.Second, cancel)

在这个例子中,我们启动一个协程开始循环做一些工作。同时这个协程也会 select监听ctx.Done() channel。 过了1秒后,我们调用cancel()方法取消context。这时候ctx.Done() channel就会被关闭,协程里的select会检测到并退出循环。

所以Done() channel给我们提供了一个在context结束时通知其他 interessiated 方的机制。我们可以用它来构建复杂的上下文和取消机制。

select 如果没有满足条件, 会阻塞运行吗

在Go的select语句中,如果没有任何case分支满足条件,select语句会阻塞。例如:


select {}  // 永远阻塞

这个select语句永远也不会有分支被选中,所以会一直阻塞。但是,如果其中至少有一个case的通信可以进行,select就不会阻塞:


chan1 := make(chan int)
chan2 := make(chan int)

select {
case <-chan1:
    println("chan1")
case <-chan2:
    println("chan2")
}

这里至少有chan1和chan2两个case可以进行通信,所以select不会阻塞,而是随机选择一个可执行的case执行。select的阻塞特性常用来实现超时效果:


select {
case <-ch:
    // ch可读,执行case
case <-time.After(1 * time.Second):
    // 1秒后超时,执行此case  
}

这里如果ch在1秒内 readable,select会选中第一个case;如果1秒后ch还是不可读的,select会选中第二个case执行,实现超时效果。

所以总结来说:

  • 如果select中没有任何一个case的通信可以进行,它会一直阻塞
  • 如果select中至少有一个case的通信可以进行,它就会随机执行一个可进行通信的case
  • 这些特性常用于实现channel的超时效果