`

java协程框架----kilim实现机制解析

阅读更多

 java语言处理多任务的模式是基于多线程,java语言级别原生并不支持协程,我们想要java语言支持协程,就需要在线程和协程之间架起一道桥梁。在某个事件点(我们成为挂起点)上,我们在应用级别备份当前任务在线程上的调用栈信息(包括局部变量和操作栈上的数据),释放线程,让它去执行下一个任务;等某些事件被触发的时候,重新执行刚才的任务,用之前备份的调用栈信息恢复线程的调用栈,从挂起点开始执行。

来看看Kilim是如何实现标示出挂起点、识别出需要备份的调用栈、备份/恢复调用栈、任务的唤醒这些功能的:

1、标识挂起点,kilim框架通过

kilim.Task.pause(kilim.PauseReason, kilim.Fiber)

接口挂起任务,对外也提供了

kilim.Mailbox.get()

 接口来实现挂起,只要把这两个接口放到需要挂起任务的地方,kilim框架就会在运行期实现此处挂起功能了。在挂起点还做了另外一件很重要的事情,就是任务作为一个Listener,会监听Mailbox,当我们在Mailbox中放入数据的时候,会触发这个监听器,唤醒任务,更详细在最后一步介绍。当然对于仅仅使用Mailbox提供的API传递数据的用户,可能没有意识到挂起动作的存在。

 

2、识别出需要备份的调用栈,Kilim通过在源码级别让方法抛出kilim.Pausable异常来标示出这个方法的运行期栈帧需要备份恢复。这个Pausable是个CheckedException,所以调用这个方法的整个静态调用链上,都需要抛出Pausable,这个这个静态链对应于运行期jvm的栈。当然,你可以把这个受查异常吃掉,导致分析路径不完整,运行期出错。

 

3、备份调用栈,即备份/恢复整个调用栈上的所有栈帧里边的局部变量和操作栈。Kilim通过编译期修改字节码,织入备份和恢复栈帧信息的字节码,而这些对源码级开发者几乎是透明的。Kilim会分析所有抛出Pausable异常的方法的字节码,通过控制流分析找到要织入代码的位置,计算行号表、try/catch/finally块地址信息、局部变量表、最大栈深、跳转指令目标位置等信息;通过数据流分析备份运行期操作栈上的数据,一些计算过程中的中间结果并没有存储在局部变量表,所以不能通过备份局部变量表实现,必须把运行期的中间结果也分析出来,备份下来,如

a + b + remote.get(…)

这里a+b只在运行期产生,但是后边的方法调用会导致任务挂起,所以需要备份a+b的值。由于在第2步的时候所有整个调用链上所有方法都抛出了Pausable方法,所以织入的时候,整个调用链上所有方法都被织入了备份/恢复代码,运行期整个操作栈上的栈帧都可以从挂起点开始被备份。

 

4、唤醒任务、恢复调用栈,在第三步我们已经把任务挂起了,但是在什么时候可以恢复执行呢?第一步在挂起任务的时候在Mailbox上注册了监听器,等有消息回来放入Mailbox的时候,放消息的那个线程会唤醒任务,任务会进入可执行任务队列,等待工作线程来调度执行。如果等待线程队列有线程在等待任务,就会唤醒一个等待线程。被唤醒的工作线程会从可执行任务队列中那任务来执行,重复以上过程直到整个任务完成。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics