gpt4 book ai didi

go - 关于取消的上下文混淆

转载 作者:IT王子 更新时间:2023-10-29 01:56:44 24 4
gpt4 key购买 nike

package main

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

func myfunc(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Printf("Ctx is kicking in with error:%+v\n", ctx.Err())
return
default:
time.Sleep(15 * time.Second)
fmt.Printf("I was not canceled\n")
return
}
}
}

func main() {
ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
defer cancel()

var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
myfunc(ctx)
}()

wg.Wait()
fmt.Printf("In main, ctx err is %+v\n", ctx.Err())
}

我有上面的代码片段,它确实打印了这样的输出

I was not canceled
In main, ctx err is context deadline exceeded

Process finished with exit code 0

我知道 context 在 3 秒后超时,因此当我最后调用 ctx.Err() 时它确实给了我预期的错误。我还了解到,在我的 myfunc 中,一旦 select 匹配 default 的情况,它就不会匹配 done 。我不明白的是,如何使用上下文逻辑让我的 go func myfunc 在 3 秒内中止。基本上,它不会在 3 秒内终止,所以我想了解 golang 的 ctx 如何帮助我解决这个问题?

最佳答案

如果您想从上下文中使用超时和取消功能,那么在您的情况下 ctx.Done()需要同步处理。

来自 https://golang.org/pkg/context/#Context 的解释

Done returns a channel that's closed when work is done on behalf of this context should be canceled. Done may return nil if this context can never be canceled. Successive calls to Done return the same value.

所以基本上是 <-ctx.Done()将在两个条件下被调用:

  1. 当上下文超时超过
  2. 当上下文被强制取消时

当这种情况发生时,ctx.Err()永远不会nil .

我们可以对错误对象进行一些检查,以查看上下文是否被强制取消或超过超时。

上下文包提供了两个错误对象,context.DeadlineExceededcontext.Timeout , 这两个将帮助我们确定为什么 <-ctx.Done()被称为。


示例 #1 场景:上下文被强制取消(通过 cancel() )

在测试中,我们会尝试让上下文在超时之前被取消,所以<-ctx.Done()将被执行。

ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))

go func(ctx context.Context) {
// simulate a process that takes 2 second to complete
time.Sleep(2 * time.Second)

// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)

select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
}

输出:

$ go run test.go 
context cancelled by force

示例 #2 场景:超出上下文超时

在这种情况下,我们使进程花费的时间比上下文超时时间长,因此理想情况下 <-ctx.Done()也会被执行。

ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))

go func(ctx context.Context) {
// simulate a process that takes 4 second to complete
time.Sleep(4 * time.Second)

// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)

select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
}

输出:

$ go run test.go 
context timeout exceeded

示例#3 场景:上下文由于发生错误而被强制取消

可能会出现这样的情况,我们需要在进程中间停止 goroutine,因为发生了错误。有时,我们可能需要在主例程中检索该错误对象。

为此,我们需要一个额外的 channel 来将错误对象从 goroutine 传输到主例程。

在下面的示例中,我准备了一个名为 chErr 的 channel .每当(goroutine)过程中发生错误时,我们将通过 channel 发送该错误对象,然后立即停止过程。

ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))

chErr := make(chan error)

go func(ctx context.Context) {
// ... some process ...

if err != nil {
// cancel context by force, an error occurred
chErr <- err
return
}

// ... some other process ...

// cancel context by force, assuming the whole process is complete
cancel()
}(ctx)

select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
}
case err := <-chErr:
fmt.Println("process fail causing by some error:", err.Error())
}

附加信息 #1:调用 cancel()上下文初始化后立即

根据 context documentation关于 cancel()功能:

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

总是调用cancel()很好在上下文声明之后立即运行。它是否也在 goroutine 中被调用并不重要。这是为了确保在 block 内的整个过程完全完成时始终取消上下文。

ctx, cancel := context.WithTimeout(
context.Background(),
time.Duration(3*time.Second))
defer cancel()

// ...

附加信息#2:defer cancel()在 goroutine 中调用

您可以使用 defercancel() 上goroutine 中的语句(如果需要)。

// ...

go func(ctx context.Context) {
defer cancel()

// ...
}(ctx)

// ...

关于go - 关于取消的上下文混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52799280/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com