当前位置:首页 >热点 >Go1.21 速览:Context 可以设置取消原因和回调函数了,等的可太久了! 速览设置数等的取消可太久了

Go1.21 速览:Context 可以设置取消原因和回调函数了,等的可太久了! 速览设置数等的取消可太久了

2024-06-26 14:12:34 [百科] 来源:避面尹邢网

Go1.21 速览:Context 可以设置取消原因和回调函数了,速览设置数等的取消可太久了!

作者:陈煎鱼 开发 前端 Context 一直是原因大家使用的最频繁的标准库之一,他联通了整个 Go 里的调函工程体系。这次在 Go1.21 对 Context 增加了 WithXXXCause 相关函数的太久错误类型支持。对于我们在 Go 工程实践中的速览设置数排查和定位,能够有一些不错的取消助力。

大家好,原因我是调函煎鱼。

在 Go 中有一个很经典的太久设计:context,这是速览设置数许多同学初学时必学的标准库。涉及到上下文传递、取消超时控制等必要项。原因

Go1.21 速览:Context 可以设置取消原因和回调函数了,等的可太久了! 速览设置数等的取消可太久了

甚至在函数体中的调函第一个参数大多是传 context。写第三方库也必须兼容 context 设置,太久否则会经常有人提需求让你支持。

Go1.21 速览:Context 可以设置取消原因和回调函数了,等的可太久了! 速览设置数等的取消可太久了

我觉得这次的新特性更新虽不复杂,但作用挺大。建议大家学习!

Go1.21 速览:Context 可以设置取消原因和回调函数了,等的可太久了! 速览设置数等的取消可太久了

Context Demo

以下是一个快速 Demo:

package mainimport ( "context" "fmt" "time")const shortDuration = 1 * time.Millisecondfunc main() {  ctx, cancel := context.WithTimeout(context.Background(), shortDuration) defer cancel() select {  case <-time.After(1 * time.Second):  fmt.Println("overslept") case <-ctx.Done():  fmt.Println(ctx.Err()) }}

运行结果:

context deadline exceeded

一切都看起来没什么问题。

麻烦点

但在实际写业务代码和排查问题时,你就会发现一个麻烦的事。在出现上下文超时或到达所设置的截止时间时,ctx.Err 方法可以获得 context deadline exceeded 的错误信息。

但这是远远不够的,你只知道是因为诱发了超时。但不知道是哪里导致的,还得再去根据访问的逻辑,再走一遍脑洞,再进行排查。又或是根据代码堆栈,再去设想,最后复现成功。

又或是查不到。因为这种一般是偶现,很有可能就留给下一代的继承者了~

又更有业务诉求,希望在出现上下文的异常场景时,可以及时执行回调方法。然而这没有太便捷的实现方式。

Go1.21 增强 Context

增加 WithXXXCause

在即将发布的 Go1.21,针对 Context 的错误处理终于有了一点点的增强,来填补这个地方的信息,允许添加自定义的错误类型和信息。

新增的 Context API 如下:

// WithDeadlineCause behaves like WithDeadline but also sets the cause of the// returned Context when the deadline is exceeded. The returned CancelFunc does// not set the cause.func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)// WithTimeoutCause behaves like WithTimeout but also sets the cause of the// returned Context when the timout expires. The returned CancelFunc does// not set the cause.func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)

与原先的 WithDeadline 和 WithTimeout 作用基本一致,唯一区别就是在形参上增加了 cause error,允许传入错误类型。

WithTimeoutCause

WithTimeoutCause 的使用示例:

tooSlow := fmt.Errorf("too slow!")ctx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Second, tooSlow)time.Sleep(2*time.Second)cancel()

像上述程序,执行 ctx.Err 方法时得到的结果是:context.DeadlineExceeded,这是既有的。

此时,我们再结合在 Go1.20 版本加入的 context.Cause 方法:

func Cause(c Context) error

就能得到对应的错误信息,上述的结果对应的是 tooSlow 变量。

WithCancelCause

WithCancelCause 的使用示例,计时器先触发:

finishedEarly := fmt.Errorf("finished early")tooSlow := fmt.Errorf("too slow!")ctx, cancel := context.WithCancelCause(context.Background())ctx, _ = context.WithTimeoutCause(ctx, 1*time.Second, tooSlow)time.Sleep(2*time.Second) // timer fires, setting the causecancel(finishedEarly) // no effect as ctx has already been canceled

对应的程序结果:

  • ctx.Err():context.DeadlineExceeded 类型。
  • context.Cause(ctx):tooSlow 类型。

先发生上下文取消的使用示例:

finishedEarly := fmt.Errorf("finished early")tooSlow := fmt.Errorf("too slow!")ctx, cancel := context.WithCancelCause(context.Background())ctx, _ = context.WithTimeoutCause(ctx, 1*time.Second, tooSlow)time.Sleep(500*time.Millisecond) // timer hasn't expired yetcancel(finishedEarly) // cancels the timer and sets ctx.Err()

对应的程序结果:

  • ctx.Err():context.Canceled 类型。
  • context.Cause(ctx):finishedEarly 类型。

增加 AfterFunc

同样的,在 Go1.21 也对 Context(上下文)被取消的动作后增加了一些增强。平时当上下文被取消时,我们只能通过启动 Goroutine 来监视取消行为并做一系列操作。

但这未免繁琐且增大了我们的编码和运行成本,因为每次处理都要 goroutine+select+channel 来一套组合拳,才能真正到写自己业务代码的地方。

为此新版本增加了注册函数的功能,将会在上下文被取消时调用。函数签名如下:

func AfterFunc(ctx Context, f func()) (stop func() bool)

在函数作用上,该函数会在 ctx 完成(取消或超时)后调用所传入的函数 f。

在运行机制上,它会自己在 goroutine 中调用 f。需要注意的是,即使 ctx 已经完成,调用 AfterFunc 也不会等待 f 返回。

这也是可以套娃的,在 AfterFunc 里再套 AfterFunc。这里用不好也很容易 goroutine 泄露。

基于这个新函数,可以看看以下两个例子作为使用场景。

1、多 Context 合并取消的例子:

func WithFirstCancel(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {  ctx, cancel := context.WithCancel(ctx1) stopf := context.AfterFunc(ctx2, func() {   cancel() }) return ctx, func() {   cancel()  stopf() }}

2、在取消上下文时停止等待 sync.Cond:

func Wait(ctx context.Context, cond *sync.Cond) error {  stopf := context.AfterFunc(ctx, cond.Broadcast) defer stopf() cond.Wait() return ctx.Err()}

基本满足了各种上下文的复杂诉求了。

总结

Context 一直是大家使用的最频繁的标准库之一,他联通了整个 Go 里的工程体系。这次在 Go1.21 对 Context 增加了 WithXXXCause 相关函数的错误类型支持。对于我们在 Go 工程实践中的排查和定位,能够有一些不错的助力。

另外 AfterFunc 函数的增加,看起来是个简单的功能。但是可以解决以往的一些合并取消上下文和串联处理的复杂场景,是一个不错的扩展功能。

苛刻些,美中不足的就是,Go 都已经发布 10+ 年了,加的还是有些太晚了。同时针对 Context 也需要有更体系的排查和定位侧的补全了。

责任编辑:武晓燕 来源: 脑子进煎鱼了 GoContext设置

(责任编辑:热点)

    推荐文章
    热点阅读