java携程kilim,Java协程框架--Kilim工作原理 - kobejayandy的专栏 - CSDN博客

概括的讲,​Kilim框架在实现这个语义特性时,干了以下几个事情:

利用字节码技术(基于ASM字节码框架),将普通代码转化为支持协程的代码;

调用P

auseable方法

的时候,如果暂停了就保存当前方法栈的State,暂停

执行

当前Task,将控制权交给Scheduler调度器;

Scheduler调度器负责协调其他就绪的Task;

之前暂停的

Task恢复

的时候,自动恢复State,恢复到上次执行的位置继续执行;

其中,第一点是在编译期实现,后面三点是在运行期执行。

再稍微详细一点就是:

Kilim通过编译期字节码编织,对每一个可暂停(Pauseable)的方法进行字节码处理,在方法执行前和执行后加上相关的执行上下文的处理,暂停时会保存整个线程堆栈,然后通过特定的字节码跳转指令goto跳转到另外一个Task的执行方法中,恢复时将复原整个线程堆栈,回到上次暂停时的位置继续往下执行。

Kilim

的工作原理

第一个Kilim最神奇的地方在于字节码增加,那么它是怎样将普通的Java代码改写层支持协程的代码呢?首先上Kilim官方文档中的一张图:

这张图也即Kilim实现协程语义的精髓所在,我们来一一分析。

左边是普通的Java函数代码,与我们常见的函数唯一有所不同的是函数a和b均显示声明抛出Pausable异常,而实际上这个异常在运行期间不会抛出,它的实际作用类似于注解,使得Kilim能够识别哪些代码需要Weaver工具进行代码增强。函数

抛出

Pausable异常即表明该函数是可暂停的,

右边的代码即通过字节码增强后的代码,与左边原始的代码相比,首先函数声明中额外增加了一个Fiber参数,Fiber可以理解为当前纤程、协程的上下文。

Fiber中存储着

协程暂停和恢复时需要用到的函数堆栈、程序计数器以及当前函数的执行状态。字节码增强后的代码以调用

Pausable方法a为分界,

将整个函数分成几个代码块,也即官网文档中提及的

prelude、pre_call、post_call

三部分。

1.在

prelude块中,也即刚进入函数a时将会执行的代码块,将根据

Fiber中的pc

程序计数器

跳转到对应的代码块处开始执行。

2.在

pre_call

块中,

也即在调用函数b之前,将调用

Fiber的down方法记录当前执行状态和pc程序计数器,标识着函数将进入下一个

Pausable方法。

3.在

post_call块中,也即在

调用

函数b之后,将

调用

Fiber的up方法计算函数b调用完成后返回的状态

,标识着从一个被调的

Pausable方法返回,它既可能是正常的函数b执行完成返回,也可能是函数b执行暂停返回,接着通过这个状态控制后续的执行流程。

这四种状态分别为:

NOT_PAUSING__NO_STATE,

即被调函数执行完成正常返回,这种情况与即普通的函数执行类似。

NOT_PAUSING__HAS_STATE,

即被调函数执行完成,但还存在上次暂存的栈帧,这种情况一般是函数从上次暂停处恢复执行,且顺利执行完成返回,此时需要恢复函数的栈帧,然后goto到RESUME代码块继续执行。

PAUSING__NO_STATE,

即被调函数执行过程中暂停,且还未保存函数栈帧,需要主调函数执行暂存操作,这种情况一般即第一次协程执行到需要暂停处

,此时需要采用字节码暂存函数的栈帧和状态,然后直接return。

PAUSING__HAS_STATE,即被调函数执行过程中暂停,且已经保存函数栈帧,这种情况是该Pausable从上次暂停处恢复执行,但是依然没有预期的结果,需要再次暂停,此时因为之前暂停时

函数栈帧和状态都已经保存过,不需要再做什么,因此直接return即可。

ok,到目前为止是不是谜底已经大概清晰,不过要把协程与线程的执行过程整个串联起来,形成一个整体还稍显迷惑,接下来详细说明。

上面已经提到协程执行过程中核心的两个点:一点是调用Pauseable方法的时候,如果暂停了就保存当前方法栈的State,暂停执行当前Task,将控制权交给Scheduler调度器,另外一点是暂停的Task恢复的时候,自动恢复State,恢复到上次执行的位置继续执行。这两点的具体过程如下:

上篇博文

Java协程框架--Kilim源码分析

中已经讲到在Kilim中有几个核心元素,包括Task、Scheduler、WorkerThread以及Mailbox。其中

WorkerThread即实际执行Task任务的工作者线程,Task即具体的可暂停的业务,Task与Task之间通过定制的

Mailbox来通信。Kilim将线程run方法体中所有嵌套层级调用的所有

Pausable方法组织成一个具有父子关系的调用链,形如run->A->B->C,通过

Task私有的

Fiber来记录执行到哪一个层级。

通常Kilim中大部分使用

Mailbox提供的get、getb、getnb三个不同版本来接收消息,其中最常用的get会阻塞当前Task而不阻塞当前线程。

那么如何实现Task的暂停

呢?

例如一个运行状态Task的调用链

run->A->B->C,其中A、B、C均为

Pauseable方法,在函数C中调用了

Mailbox的get方法且设置了超时时长,当整个链嵌套执行到C的

get方法这一行时,因为

get本身也是一个

Pausable方法,如果没有接收到消息,将会把Task作为该

Mailbox的观察者,并调用Task.pause(this)方法暂停自身,然后该get方法即直接返回,

get调用返回后,C根据

Fiber的up计算

发现是暂停返回,则也暂停本函数,暂存栈帧和状态,直接返回,如此逆向直到run方法,从而实现Task的暂停。

那么又如何实现Task的恢复呢?

Task暂停的过程中有一步很关键,将该

Task作为该

Mailbox的观察者,在当有其他线程把消息通过调用

Mailbox的put方法添加到

Mailbox中时,或者超时定时器触发时,将会回调该

Mailbox的观察者,告诉观察者有新的消息到来。这样Task的onEvent被回调,

onEvent直接调用resume方法,而resume方法实际最重要的一步即调用

Scheduler的schedule方法,将该Task加入到

Scheduler的可运行任务队列中,并随机选择一个等待运行的工作者线程,并notify

该线程,该线程被

唤醒后将执行该Task,重复之前的函数

调用链

run->A->B->C执行,由于

A、B、C三个函数中均已经保存了之前暂停的函数栈帧和状态,因此之前已经执行过的代码块将不会重复执行,会根据Fiber中状态选择性的执行对应的代码块。因为

Mailbox中已经有消息,因此再重复执行到get方法时能够直接获取到消息,正常的往下继续执行。这样相当于又走了一次

调用链,但是并非重复执行已经执行过的代码,而是恢复执行之前未执行的代码,从而实现

Task的恢复。

public void onEvent(EventPublisher ep, Event e) {

resume();

jqm视频播放器,html5视频播放器,html5音乐播放器,html5播放器,video开发demo,html5视频播放示例,html5手机视频播放器

xmt1139057136

43055次阅读

06-21

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值