关于《Go 是否应在下个版本中去掉上下文》的个人看法

2023-11-18 23:42:10 浏览数 (1)

最近go社区一直在热议是否应该在下个版本中去掉上下文,将上下文集成到 io.Reader,以便在上下文取消时中止读取操作。如果GO真在下个版本中去掉上下文,那么 io.Reader 就会变成这样:

代码语言:txt复制
type Reader interface {

        Read(ctx context.Context, p []byte) (n int, err error)

}

读取流数据或者大文件是很常见的时间损耗任务,因此这建议看起来似乎是个不错的主意。

但是我却不是这样认为的。

1.Go 不是服务端专用语言

首先,Go 虽然很适合用来写服务端,但它本身定位应该是通用型语言(general purpose language),而不是服务端专用语言,而上下文基本都是写服务的时候使用。

2.上下文是病毒

上下文在程序中的传播就像病毒一样,跟随着请求的调用路径,每个函数都要将它作为第一个要传递的参数。

更糟糕的是,它还会扩散到不同的包。想象一下,如果一个 Go 代码库在执行中存在时间损耗(例如前面提到的 io 或者 sql 等等),且它可能会被用于服务端程序,那么它就应该支持上下文。如此一来,所有人都要考虑上下文了,即使你并不需要它!这又违背了 Go 是通用型语言这一点。

3.WithValue 不是个好东西

ctx.Value存在很多缺陷(这是 context 提供的一种在上下文中传递数据的机制),并给出了如下理由:

  1. 它传递的值不是静态类型(使用反射实现)
  2. 使用的时候,需要给函数写注释,说明函数用到了哪些键和值
  3. 使用场景很像线程本地存储(thread-local storage),这玩意儿很糟糕,不灵活,使用复杂,测试麻烦……
  4. 太魔法了,很容易出 bug

ctx.Value 的确能让一些事情变得简单,但他相信,设计 API 的时候,还是应该避免使用它,总能找到替代方案的。

4.代码看上去太不优雅

现在创建一个上下文变量看起来已经很傻了:

代码语言:txt复制
ctx context.Context

很像 Java:

代码语言:txt复制
Foo foo = new Foo();

而这种事情是 Go 最开始创建出来就想要避免的。

如果把上下文再引入标准库的 io 包:

代码语言:txt复制
n, err := r.Read(context.TODO(), p)

更加不方便

5.goroutine 取消的难题

**context 这个包到底解决了什么问题?**

很简单,就是如何取消(cancel)协程。在 context 包出来之前,有过一个讲座专门谈论协程的取消问题,解决方案就是用原生 channel 实现,但伸缩性较差。但主要问题是:

  1. 别的库不会接收 cancel channel,所以只能在不同的操作之间做取消
  2. 设想一个 goroutine 树,要直接取消整个树是很简单的,但如果要取消一个子树,你还需要定义一个新的 cancel channel

随后 context 包的出现,解决了这两个问题,尽管它存在问题,但目前仍是最好的方案。

6.Go 应该从语言层面解决取消难题

我觉得,Go 应该在第 2 版中在语言层面解决「取消难题」。Go 已经让我们很方便地创建协程,以及在协程间通讯,但 context 包的存在,证明了 Go 还不能很好的处理协程取消的问题,这是 Go 的一个弱点。

对于新的解决方案,需要满足以下几条:

  • 简洁优雅
  • 可选性,非侵入式,非感染式
  • 健壮且高效
  • 只解决取消的问题,忽略 Values

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞