Warning: Undefined global variable $debug in /var/www/ourcoders/tiny4cocoa/application/controllers/baseController.php on line 124
有个梨UGlee 2020-01-16 18:12:20 发布的技术动态 - OurCoders (我们程序员)
有个梨UGlee
2020-01-16 18:12:20 发布
说一下Dart。

Dart和Node几乎是一样的运行模型,全异步io加事件驱动;但是Dart有三个显著的优势,第一,它有类型,第二,它是编译执行的,第三,它使用isolate支持多线程。

在Node里如果要支持多线程以应对密集计算,需要启动一个新的vm,有显著的内存开销,在这个问题上Dart完胜。

Dart的类型系统支持让它回到了class-based inheritance上,类似Java,而不是JavaScript;其实有点儿遗憾,prototype-based inheritance在语言模型上更纯粹,但是它有一点效率问题,Dart的“退步”可以看作一种工程上的取舍,也不会造成太严重的问题。

++++

在有isolate支持下,isolate之间的通讯只能是异步的;最基础的设计要求应该是可以在isolate之间建立stream,尤其是在一个线程里创建的stream可以pass到另一个stream里,相当于golang或者pi-calculus里的pass channel over channel,这是asynchronous pi里最重要的设计原语,应该在语言级有良好支持。

举个例子。

比如http server有formdata,或者更宽泛的说,在server端demux一个stream,这时最好的办法是能够创建一个sub-stream对象(source),把它交给另一个isolate/thread继续处理。

stream在异步编程里的重要性几乎和语言里的原始类型接近,也是node吃饭的家伙。它可以看作channel概念在事件模型下的实现。实际上大量的调度工作都可以抽象成stream,例如在node里有object stream的概念,其中的object可以是一个job request(可包含一个callback),统一的stream设计可以处理buffer, delay, debounce, batch等等异步通讯里的常见问题,在node里实现一个stream的设计非常出色。

++++

asynchronous vs synchronous是一个并发的本质问题。

这一对概念不仅仅在编程尺度上使用,在网络尺度,在操作系统的底层,CPU和总线设计等等地方均适用。

几年前在基于Petri Net模型设计并发组合时问过Lo姐(@红烧Lo)一个问题,基于synchronous组合的,当时Lo姐反问了一个问题就是为什么这个组合要求同步而不是异步?当时我回答不出来。

支持同步的证据是Inria大学的一些研究,可以在Google scholar上搜索Synchronous Method找到;另外类似方法在硬件设计上广泛使用,本质上它是IO状态机组合时的同步迁移,IO状态机是经典状态机的一个拓展,状态机组合不是靠状态空间相乘实现,而是靠同步IO强制在指定的状态共同迁移做到。这种迁移,实际上在缩减状态空间组合,而状态机具有可验证性,尤其是硬件的状态空间不大时。

++++

但是Synchronous的迁移在应对设计变化时非常不灵活。

常见的情况是,如果出现一个设计变更,一个数据源突然变成了需要异步读取才能获得的,这个时候同步状态机必须修改,有可能波及的范围很大;这是实际项目中很常见的情况,比如一个原来在内存里存储的数据,因为数量增加,被临时持久化到磁盘上,这个时候在内存中的读取如果似乎cache hit可以同步继续,但如果cache miss,必须等一次io,这是无法遇见的。

Synchronous迁移设计的一个好处也需要强调一下,就是对现代处理器而言,所有的同步迁移,在代码上都可能因为激进的inline和jit,获得显著的性能提升。这是Node的emitter, callback, 和stream的高性能的法宝之一。

Asynchronous则更加灵活,而且能更好的处理一些常见问题,例如buffer, debounce和batch等等,实际上这些都是面向通讯(io)编程的必要构件。

++++

但是当你问到正确性时。

我们举个例子,譬如file stream和socket stream都有一个flush方法,但是当很多stream pipe起来之后,这个flush是没有办法在底层很好实现的。

在这里"worse is better"哲学发挥效力的地方是,你不要试图构建完美底层,远端是否正确收到了全部数据应该考虑在上层协议上实现,例如对方应答一下收到的数据大小甚至checksum,然后close stream,而不是依赖一个flush的使命必达;在io里唯一需要使命必达的是顺序,而这一点,也是在tcp而不是ip层实现的。

所以设计上并非无法验证,而是设计假设变成“只要发送者的output是按照出发的顺序抵达接受者的input,那么这个程序是可以正确工作的”。而在设计上需要考虑的pitfall就是那些客观存在情况,顺序出发的操作请求被乱序执行。

例如你在node里同步发出两个文件操作,你并不清楚哪个会是最终结果,甚至他们互相之间有干扰。

设计上需要保证即使存在这种乱序执行,仍然可以得到想要的结果。这也是并发编程的本质。

++++

All in all,对于synchronous,理论上,如果编译器很聪明,它可能发现一个io是可以优化掉的,就像inline可以优化掉function call一样;但是asynchronous,它就困难了(但不是不可能)。在这个时候,数学变换能够适用,性能收益极大。

但是asynchronous,是在各个尺度上普适的,它很灵活,效率不是那么好,有一些pitfall,但仍然是可以保证正确性的,毕竟你可以等待一个应答再继续操作。