乐趣区

关于后端:Go121-速览Context-可以设置取消原因和回调函数了等的可太久了

大家好,我是煎鱼。

在 Go 中有一个很经典的设计:context,这是许多同学初学时必学的规范库。波及到上下文传递、超时管制等必要项。

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

Context Demo

以下是一个疾速 Demo:

package main

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

const shortDuration = 1 * time.Millisecond

func 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)

与原先的 WithDeadlineWithTimeout 作用基本一致,惟一区别就是在形参上减少了 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 cause
cancel(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 yet
cancel(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 也须要有更体系的排查和定位侧的补全了。

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。

Go 图书系列

  • Go 语言入门系列:初探 Go 我的项目实战
  • Go 语言编程之旅:深刻用 Go 做我的项目
  • Go 语言设计哲学:理解 Go 的为什么和设计思考
  • Go 语言进阶之旅:进一步深刻 Go 源码

举荐浏览

  • Go1.21 速览:新内置函数 clear、min、max 和新规范库包 cmp!
  • Go1.21 速览:过了一年半,slices、maps 泛型库终于要退出规范库。。。
  • Go1.21 速览:Go 终于打算进一步反对 WebAssembly 了。。。
退出移动版