大家好,我是煎鱼。
在 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/blo… 已收录,学习 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 了。。。