大家好,我是煎鱼。
在 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)
与原先的 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 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 了。。。