存档

2019年6月28日 的存档

说说golang的使用体验

2019年6月28日 没有评论

说说golang的原罪。

在说golang的原罪之前说说golang的设计优点,自包含之类的工程优点就不说了,新语言总归要去掉一些老语言的错误的。

golang的最大特点是引入了channel和goroutine。goroutine不是fiber/coroutine/lightweight thread,它概念上是和scheme/js一样把function scope当作struct/object/context处理,也就是拆掉了传统意义上的栈计算,因为context switch其实就是在切换stack。这对它的高并发设计目标是很重要的。当然拆栈不是没代价,拆栈的结果是副栈也没了,所以try catch也报废了。

channel是golang的标志特性,大家都说在golang里channel是一等公民。我先不反对这句话。

在CSP里,channel本质上是把共享内存变成通讯,或者说私有内存模型,这牺牲效率但是比锁更容易使用,因为锁是分布在代码里的,不容易维护,也难以调试隐蔽错误。虽然两者逻辑上是可以等价的。

但golang不是严格的CSP,它不仅仍然支持锁和共享内存,同时channel可以通过channel传递,这在CSP里是没有的,在ccs里也没有,只有到了π里才有了这个东西,当然这个做法是好的,这是非常重要的特性。

—-

那么说到原罪,不熟悉π的人可能难以理解。channel是什么?你可能说是通讯啊。但是channel不只是通讯。

我们常常这样比较imperative语言和functional语言:imperative语言里,例如c,分号是一个组合过程的符号,即前面一个命令结束,后面一个命令开始。它显式定义了执行序。但是在纯函数式语言里,情况不是这样的,执行序不体现在代码书写顺序上,而是体现在估值依赖性上,即大家常说的lazy。

理解了这一点你就可以明白channel在π里的意义,即使你一点也没有学过π;channel是π里「唯一」表述过程执行顺序的东西,即消息的到来触发执行。

实际上有了do的haskell,已经有了π的影子,虽然没有显式定义channel;而golang的问题就是,它给了你两种方式定义执行序,一种是响应消息,另一种是传统的命令式,这是混乱的原因,程序员们甚至没意识到这是个问题。

它为什么是问题呢?

因为就像图灵机和lambda是等价的一样,π是并发的完备模型,如果你有其它模型,它一定也是和π等价的;而并发系统就是反应式系统,这两者也是等价的,换言之,当你说并发系统时,这个系统的设计和实现就是反应式的,而唯一触发反应的方式,是消息的到来,无论是外部消息还是内部消息。从这个意义上说,golang完全是个半成品设计。channel在被用作一个辅助性的工具特性,而不是主导整个程序的反应式的过程依赖性框架。

golang是不是一个高并发语言呢?答案是:它对“高”的解决办法(拆栈)很可能是不错的,但是它对“并发”的设计,一泡污,连js都不如,js起码在代码层面,这种反应式的过程依赖性是一目了然的。

—-

最后补充一下什么是反应式的try catch。

反应式的try catch和反应式系统里调用function是一样的。message系统本来都是单向工作的,如果要模拟一个类似function的round trip,或者说request/response,在π里的表述是,在单向message里包含了一个channel,这样不管这个message怎样逐层传递,最终总有人要在channel里返回结果,如果玩儿崩了,也要有人负责用这个channel返回错误。

典型的例子就是HTTP协议的设计:如果服务器内有代码玩儿完了,而服务没死,你还可以拿到一个500错误,如果服务器也跑路了,客户端的HTTP协议栈或者TCP协议栈还可以返回一个connection或socket错误。这就是并发系统里的try catch。因为π建模的系统不限于一个程序或者一个物理机实例,对分布式系统同样适用。所以golang里屎一样的错误处理机制没什么好为之辩护的,有种你干脆扔掉HTTP甚至TCP,全用UDP干了,你才有资格说golang的错误处理设计是对的。

 

 

本文来自微信群讨论

分类: golang 标签:
互联网安全