2019-01-13

毕竟人类

考虑一种调用风格.

Promise.costly(()->receive())
.transform((packet)-> mmap.transferFrom(packet))
.sidekick(()->logging.info("saved"))
.catching((exception)->logging.error("save fail",exception))
.addListener((channel)->channel.close())

这里大致是对CompletableFutre和guava对ListeningFuture的一些包装.
稍微链式了一下,以及改动了点语义.

大致涵盖的几个data flow是.
一个正常情况下的receive packet -> save -> close.
一个sidekick是在正常receive之后的并行log部分.
一个是receive exception情况下的catching部分.
以及不管是正常还是异常情况下都并行触发的addListener部分.

最终返回的reference的value是跟transform返回的签名一致.
也就是除了transform,其他都是旁路性质.

这点跟completable是不太一样的.
虽然底层实际用的就是completable future.

这么做的原因主要是不想总是try catch.
或者像go一样检查返回值.

而且为了简单,lambda的声明都是带throw的对应的functional的版本.

所以实现上是把最初的exception一路propagate过来的.
这点跟completable/listening都算一致的.

问题主要是这个compute graph的trigger或者说traversal方式.
或者说transform这些callback的调用应该是同步还是异步的问题.

因为底层实际上就是Comp了table future.
所以实际上就是thenApply之类的调用的.

如果同步的话,实现上是会当场back trace的.

也就是当一个这个graph当中的一个stage完结之后会尝试看依赖项目是否也是可以执行.

这里的一个好处就是execution path可能尽可能地总体时间短.
因为基本上算是instant的.

这样的话,几率上来说对cache可能也会有点好处.

但这样的话为什么不直接写成正常的平坦的workflow呢.
而且可以没有那些基本的代价开销.

一个理由可能就是整体流程比较长.
或者存在一些不太确定的blcoking的部分.
所以通过这种人工的方式拆分出time slice.

某种程度上来说跟go的goroutine/chan/syscall调用时候隐切换是类似思路.
通过一种人为的软调度去提高throughput.

但是同时带来的一个问题就是可能latency相对会高一些.
因为是每个execution context的slice都会被切分并在不同的实际被queue或者stack.

所以,即使是说用async的方式处理stage.
但同样地还有是用first in first out还是last in first out的形式.

像Stream API的parallel使用的common pool就是forkjoin pool的last in first out.

这个在某种程度上来说是兼顾througput和latency的一个选择.
毕竟stack的距离可能没有queue的距离来得远,每个切换的数据访问的差异可能相对没那么大.

至少意图上来说,是有一定的cache friendly的.

但是这里同样会有个问题.

比如
LongStream.range(0,regions).paralle()
.mapToObject((region)->file.map(region))
.flatmap((region)->region.pages())
.map((page)->crc.update(page))
.collect()
这类flatmap的stream generation.

可能隐式地来说,会有一个形式上的synchronize point.
因为pipeline的某个节点是generator,而generate的动作可能又是有一定开销的.
比如这里的mmap,基本上手受限于磁盘的.

这样的话forkjoin thread的local queue/stack就应该都是这些相对比较耗时的task.

也就是说,对于其他task来说,除了enqueue/dequeue和queue work load的影响之外.
还收到来自未来的运行时的不预期的一些开销因素.

所以相对来说,LIFO的不确定性比较高.

另外一点就是对于原生Future这类从设计一开始就没有考虑callback的wrap方式.

guava的actor暴露的是一个ListenInPool的选择.
也就是扔到一个你提供的线程池里blocking get,然后再回调.

在实现的时候开始也采用了类似的做法.
类似Promise.costly就是在一个cache thread pool.
而对应的Promise.light才是在一个forkjoin pool里.

一个不太好的情况就是类似之前go的syscall造成的大量thread的问题.

把future listen在一个cache thread pool的问题就是可能某个瞬间产生大量future的话,会意外地尝试非常多的thread.

当然,一个思路就是限制thread数量.
但是引入cache pool的原因就是想避免一些意外bug或者intended的不会返回的future耗尽thread的问题.

折中的办法是先把future queue起来.
然后类似早期go的net poller,定期的poll遍历touch一下.

代价就是有额外的这个poll的delay.

另外一个模仿点就是timeout.
基本上就是select timeout channel的future形式.
scheudle一个delay的future cancle.

可能本质上来说,就是在各种模仿.

毕竟人类.

聊聊卡布里尼

最近看了部片叫卡布里尼,算是可能这段时间来比较有意思的一部电影. 故事也不算复杂,就是一个意大利修女去美国传教,建立慈善性质医院的故事. 某种程度上来说,也很一般的西方普世价值主旋律. 但是如果换一套叙事手法,比如共产国际的社会主义革命建立无产阶级广厦千万间的角度来看的话,也不是...