英文:
How do we guarantee that a cancelled context will lead to a goroutine's termination?
问题
假设发生以下情况:
-
我们有下面的
Consumer函数,在一个goroutine中运行。 -
另一个goroutine在
intChan通道上发送整数,没有任何延迟。换句话说,在每次for循环迭代时,都有一个值准备好在intChan上接收。 -
启动
Consumergoroutine的goroutine已经取消了传递给Consumer的上下文。因此,ctx.Done()通道也有一个准备好接收的值。
问题:
-
在这种情况下,select语句的两个case都准备好运行。
-
根据Go之旅的说法,由于两个case都准备好运行,
select会随机选择一个case。 -
我们如何确保
select不会一直选择<- intChan的case?如果在每次for循环迭代中两个case都准备好,我们如何知道最终会选择<- ctx.Done()的case?
func Consumer(ctx context.Context, intChan chan int) {
for {
select {
case <-ctx.Done():
return
case i := <-intChan:
foo(i)
}
}
}
我尝试在下面的程序中使用Consumer函数。在运行这个程序的多次运行中,Consumer和Producer goroutine总是终止。
为什么我们不会出现<- ctx.Done()的case从未执行的情况?
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(2) // 添加2个,因为我们启动了2个goroutine
Producer(ctx, &wg)
fmt.Println(time.Now())
time.Sleep(time.Second * 5) // 5秒后取消上下文
cancelFunc()
fmt.Println("CANCELLED")
wg.Wait() // 等待生产者和消费者goroutine终止
fmt.Println(time.Now())
}
func Producer(ctx context.Context, wg *sync.WaitGroup) {
intChan := make(chan int)
go Consumer(ctx, intChan, wg)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case intChan <- 1:
}
}
}()
}
func Consumer(ctx context.Context, intChan chan int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case _ = <-intChan:
}
}
}
英文:
Suppose that the following situation occurs:
-
We have the
Consumerfunction below, running in a goroutine. -
Another goroutine is sending integers on the
intChanchannel without any delay. In other words, on every iteration of the for-loop, there is a value ready to be received on theintChan. -
The goroutine that started the
Consumergoroutine, has cancelled the context passed into theConsumer. Hence, thectx.Done()channel also has a value ready to be received.
Question:
- In this situation, both the cases of the select statement are ready to run.
- According to the tour of Go, the
selectwill pick one case randomly, since both are ready to run. - What's the guarantee that the
selectwon't keep picking the<- intChancase? How do we know that the<- ctx.Done()case will eventually be selected, if both cases are ready in every iteration of the for-loop?
func Consumer(ctx context.Context, intChan chan int) {
for {
select {
case <-ctx.Done():
return
case i := <-intChan:
foo(i)
}
}
}
I've tried using the Consumer function, in the program below.
Both the Consumer and Producer goroutines always seem to terminate, in several runs of this program.
Why don't we end up with runs where the <-ctx.Done() case is never executed?
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(2) // add 2, because we spawn 2 goroutines
Producer(ctx, &wg)
fmt.Println(time.Now())
time.Sleep(time.Second * 5) // cancel the context after 5 seconds
cancelFunc()
fmt.Println("CANCELLED")
wg.Wait() // wait till both producer and consumer goroutines terminate
fmt.Println(time.Now())
}
func Producer(ctx context.Context, wg *sync.WaitGroup) {
intChan := make(chan int)
go Consumer(ctx, intChan, wg)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case intChan <- 1:
}
}
}()
}
func Consumer(ctx context.Context, intChan chan int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case _ = <-intChan:
}
}
}
答案1
得分: 1
没有保证。保证终止的最简单方法是在选择语句之外使用ctx.Err()检查错误。将错误返回给传递上下文的代码也是常见的做法。我会像这样编写Consumer函数:
func Consumer(ctx context.Context, intChan chan int) error {
for ctx.Err() == nil {
select {
case <-ctx.Done():
case i := <-intChan:
foo(i)
}
}
return ctx.Err()
}
英文:
There is no guarantee. The simplest way to guarantee termination would be to check for error with ctx.Err() outside the select statement. It is also common to return the error back to the code that passed the context. I would write the Consumer func like this:
func Consumer(ctx context.Context, intChan chan int) error {
for ctx.Err() == nil {
select {
case <-ctx.Done():
case i := <-intChan:
foo(i)
}
}
return ctx.Err()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论