【翻译】Context should go away for Go 2

2017/08/06
 
每次blog.golang.org更新博客,我都迫不及待去读一下;最新的一篇, Contributors Summit,记录了Go贡献者们的一些讨论。我读到一句话,让我感觉得有必要写这个Blog:
 
For instance, it would be nice if io.Reader accepted a context so that blocking read operations could be canceled.(io.Reader接收一个context来取消阻塞的读操作是一个不错的主意)
 
本人瞬间惊呆了,这样一来,io.Reader将会是这样:
type Reader interface { Read(ctx context.Context, p []byte) (n int, err error) }
 
我搜索了一下,发现已经有人向Go2提了这个修改;谢天谢天大多数人并不认可(proposed this change )
 
这篇博客将会讨论所有有关“context”的错误(其实 context还是挺有用的 ),以及Go2需要为它做些什么。
 
Go是一门通用的语言
 
首先我们要明确,Go是一个很好的开发Server程序的语言,但Go并不是专为写Server程序而生。Go是一门通用的语言,就像c,c++,java或python一样。比如我已经使用Go两年了,但却没有写过一个Server程序。
 
因此,我们要以设计一门通用语言的角度来设计Go及其标准库。现在,我想说的是context只适写Server的人,至少大多数情况下如此。
 
Context像病毒一样传染
 
这是context最主要的问题:它会传染!正如这篇博客中所说的(this blog post about the context package
 
At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.
(在谷歌,我们要求把context应用到请求从输入到输出所流经的所有函数上面)
 
所有的这些函数都需要传递一个context进去,否则它可能不能被完全Cancel掉。这意味着所有调用的其它库的函数也需要接收context。
 
简而言之,如果你想写一个库,而这个库会被其它的Server程序调用到,那么你就需要在函数中加入context参数!
 
这就是context如何像病毒一样传播。这样有什么坏处呢,我们来看一下:
 
1.Go是一门通用的语言
2.如果一个库会被Server程序调用,它就需要接收context。
3.现在,每个人必须处理context,即使你并不需要它。
 
当然,你可以到处传递context.TODO(),但这简单太糟糕了,它破坏了代码的可读性,让代码看起来丑陋无比,泯灭了我写Go代码的乐趣。
 
如果某天我不得不写出这样的代码:
 
n, err := r.Read(context.TODO(), p)
 
那伙计麻烦给我把枪,让我们Say Goodbye.
 
你也许会辩解:一个库可以为每个函数提供两个版本,一个带context一个不带。额……是的, "database/sql"包就这样做了。尽管它部分解决了这个问题,但看起来很糟糕(不够优雅啊)
 
并且,如果去给学生们教授Go语言,你开始讲解context版的io.Reader接口。学生们问:ctx context.Context是什么东西老师?答案可能会这样:不要管这个,只要传个context.TODO()进来就行了。听起来像 public static void给我的感觉。
 
这里的主要观点是:Context会像病毒一样传播,当我不需要它的话,我不想处理它。
 
“context”包本身的实现并不好。
 
这是一个个人观点。对我来说,context.Context接口有过多的方法。但更主要的问题是:
 
在我的公司里使用ctx.Value,你就会被炒鱿鱼!
 
我不知道是谁想出这个想法让context带上一个无意义的map,(object->object的映射),这有太多问题,我们来列举之:
 
1.很明显的一个,它不是静态类型;
2.它需要记录,哪些Value是哪个函数支持和使用的。我们知道,documentation是永远不会执行的代码。
3.它跟thread-local存储类似。我们知道thread-local存储是多么糟糕的想法。不灵活,使用,测试复杂
4.这不常发生,但是会发生名称冲突。
5.这像一个容易出错的魔法。
 
我知道,ctx.Value会使一些东西实现起来简单。但是我相信,设计API时不使用ctx.Value,你也一定会有替代方案。
 
Context是一个低效的链表。
 
WithCanel,WithDeadline工作需要创建链表。我们需要给WithCancel创建一个goroutine,将cancel信号由前一个context传递到下一个。当然如果context一直没被取消,goroutine就一直存在(相当于resource leak作者认为)
 
最后:
 
ctx context.Context
就像
Foo foo = new Foo();
一样,是Go设计中就极力避免的东西(created to avoid.)。
 
"context"包实际解决了什么问题?
 
即使有这么多的问题,"context"依然是很有用的,因为它解决了Go里面很难处理的一个问题:cancelation(取消)。这是"context"唯一所解决的问题。
 
我们正视现实,Go里面的取消很难实现。在
‘Advanced Go Concurrency Patterns’中有一个很全面和深入的讨论。这个讨论发生在context包引入到Go中之前。因此它讨论的是使用channel来解决cancelation的问题。
 
这个讨论中所提出的解决方案没有可伸缩性,原因如下:
1.cancelation使用的channels不能传递到其它的库或函数,因此只有在内部自己使用。
 
2.想像一个goroutine的树(子gotoutine由父生成),要结束所有的goroutine很容易,只要将cancelation channel关闭即可。但是要结束一个子树却很难。(你需要使用另一个channel,或其它解决方法)
 
"context"解决了这个问题,虽然效率低下且存在很多问题。但这个解决方法却要比已存在的其它办法要好。
 
 
go里我们必须要解决cancelation的问题。当我们使用goroutine时这是必要的。
 
Go2必须正视cancelation的问题!
 
我认为Go提供一个"context"这样的包,本身就是个错误。Go设计的很简单的实现了创建goroutine并在他们之间通信,但是"context"证明Go将goroutine的取消实现的很难用。我认为这个问题需要在语言层面来解决。Go需要在语言层面提供一个解决方案,达到:
 
1.简单,优雅
2.可选 ,非侵入,并非传染
3.健壮,高效
4.只解决cancelation的问题。像Values的功能可以省略了。可以在非常简单的cancelation上实现超时功能。
 
你可能会说:我喜欢context,它不需要改变或使Go语言复杂化,而优雅的解决了这个问题。我不同意。就像所有上面所说的,它不是一个优雅的解决方案,尽管cancellation不是这个语言不可获缺的一部分,但它会变得越来越重要。
 
我想了几种解决方案,但我会在另一篇博客中来写,或者如果有人提出更好的解决方案我会自己保留。这篇博客的目的是指出这个问题。
 
结论:
 
这篇博客试图指出Go语言存在的问题。简而言之,Go语言存在cancelation难的问题,并且"context"包没有很好的解决这个问题。除了语言层面,我没有想出其它解决这个问题的方法。交给Go2来做吧!
 

原文地址:https://www.cnblogs.com/gm-201705/p/8186317.html