英文:
How do we guarantee that a cancelled context will lead to a goroutine's termination?
问题
假设发生以下情况:
-
我们有下面的
Consumer
函数,在一个goroutine中运行。 -
另一个goroutine在
intChan
通道上发送整数,没有任何延迟。换句话说,在每次for循环迭代时,都有一个值准备好在intChan
上接收。 -
启动
Consumer
goroutine的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
Consumer
function below, running in a goroutine. -
Another goroutine is sending integers on the
intChan
channel 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
Consumer
goroutine, 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
select
will pick one case randomly, since both are ready to run. - What's the guarantee that the
select
won't keep picking the<- intChan
case? 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()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论