Professional Documents
Culture Documents
在Java中使用协程 毕玄
在Java中使用协程 毕玄
在Java中使用协程 毕玄
在 Java 中使用协程(Coroutine)
在讲到具体内容之前,不能不先讲下 Coroutine 的一些背景知识,来先具体了解下什么是
Coroutine。
1. 背景知识
现在的操作系统都是支持多任务的,多任务可通过多进程或多线程的方式去实现,进程和线程
的对比就不在这里说了,在多任务的调度上操作系统采取抢占式和协作式两种方式,抢占式是指操
作系统给每个任务一定的执行时间片,在到达这个时间片后如任务仍然未释放对 CPU 的占用,那么
操作系统将强制释放,这是目前多数操作系统采取的方式;协作式是指操作系统按照任务的顺序来
分配 CPU,每个任务执行过程中除非其主动释放,否则将一直占据 CPU,这种方式非常值得注意的
是一旦有任务占据 CPU 不放,会导致其他任务”饿死”的现象,因此操作系统确实不太适合采用这
种方式。
说完操作系统多任务的调度方式后,来看看通常程序是如何实现支持高并发的,一种就是典型
的基于操作系统提供的多进程或多线程机制,每个任务占据一个进程或一个线程,当任务中有 IO 等
待等动作时,则将进程或线程放入待调度队列中,这种方式是目前大多数程序采取的方式,这种方
式的坏处在于如想支持高的并发量,就不得不创建很多的进程或线程,而进程和线程都是要消耗不
少系统资源的,另外一方面,进程或线程创建太多后,操作系统需要花费很多的时间在进程或线程
的切换上,切换动作需要做状态保持和恢复,这也会消耗掉很多的系统资源;另外一种方式则是每
个任务不完全占据一个进程或线程,当任务执行过程中需要进行 IO 等待等动作时,任务则将其所占
据的进程或线程释放,以便其他任务使用这个进程或线程,这种方式的好处在于可以减少所需要的
原生的进程或线程数,并且由于操作系统不需要做进程或线程的切换,而是自行来实现任务的切换,
其成本会较操作系统切换低,这种方式也就是本文的重点,Coroutine 方式,又称协程方式,这种方
式在目前的大多数语言中都有支持。
Actor
act 方法
进行一些处理
创建并执行另外一个 Actor
通过消息 box 阻塞获取另一个 Actor 执行的结果
继续基于这个结果进行一些处理
在支持 Continuations 的情况下,可以做到消息 box 阻塞时并不是进程或线程级的阻塞,而只是
Actor 本身的阻塞,并且在阻塞时可将所占据的进程或线程释放给其他 Actor 使用,Actor Model 实
现最典型的就是 erLang 了。
毕 玄
对于 Java 应用而言,传统方式下为了支持高并发,由于一个线程只能用于处理一个请求,即使
是线程中其实有很多 IO 中断、锁等待也同样如此,因此通常的做法是通过启动很多的线程来支撑高
并发,但当线程过多时,就造成了 CPU 需要消耗不少的时间在线程的切换上,从而出现瓶颈,按照
上面对 Coroutine 的描述,Coroutine 的方式理论上而言能够大幅度的提升 Java 应用所能支撑的并
发量。
Actor
Actor 的消息发送机制
Actor 的消息接收机制
1
http://weblogs.java.net/blog/forax/archive/2009/11/19/holy-crap-jvm-has-
coroutinecontinuationfiber-etc
毕 玄
receive{
case MessageObject(args) => doHandle(args)
}
react{
case MessageObject(args) => doHandle(args)
}
receive 和 react 的差别在于 receive 需要阻塞当前 Java 线程,react 则仅为阻塞当前 Actor,但
并不会阻塞 Java 线程,因此 react 模式更适合于充分发挥 coroutine 带来的原生线程数减少的好处,
但 react 模式有个缺点是 react 不支持返回。
Task
Task 的消息发送机制
mailbox.put(messageObject); // 同步发送
mailbox.putnb(messageObject); // 异步发送
mailbox.putb(messageObject); // 阻塞线程方式发送
Task 的消息接收机制
在处理时,仅需调用以下代码:
processor.start();
从以上代码来看,Kilim 对于 Java 人员而言学习门槛更低,但对于需要采用 coroutine 方式执行
的代码在编译完毕后,还需要采用 Kilim 的 kilim.tools.Weaver 类来对这些已编译出来的 class 文件
做织入,运行时需要用织入后生成的 class 文件才行,织入的方法为:java kilim.tools.Weaver –d
[织入后生成的 class 文件存放的目录] [需要织入的类文件所在的目录],目前尚没有 Kilim IDE Plugin
可用,因此 weaver 这个过程还是比较的麻烦。
Actor 同步调用
n Scala
var senderResult=””
receive{
case result:String => {
senderResult=result
}
}
println(senderResult)
}
}
class NetSenderActor extends Actor{
def act(){
case command:String => {
sender ! (“received command:”+command)
}
}
}
但这两种方式其实都不好,因为这两种方式都会造成当前 Actor 的线程阻塞,这也是因为目前
Scala 版本对 continuations 尚不支持的原因,Scala 2.8 版本将提供 continuations 的支持,希望到时
能有不需要阻塞 Actor 线程实现上述需求的方法。
n Kilim
this.result=result;
}
public void execute() throws Pausable,Exception{
result.put(“result from TaskB”);
}
}
Kilim 的 Mailbox.get 并不会阻塞线程,因此这种方式是完全满足需求的。
n Scala
n Kilim
3. 性能、所能支撑的并发量对比
在对 Scala、Kilim 有了这些了解后,来具体看看采用 Scala、Kilim 后与传统 Java 方式在性能、
所能支撑的并发量上的对比。
测试模型
毕 玄
实现代码
实现代码以及可运行的环境请从此处下载:http://www.bluedavy.com/open/benchmark.zip
结果对比
TPS 的对比结果如下:
Load 的对比结果如下:
毕 玄
4. 参考资料
1. http://en.wikipedia.org/wiki/Computer_multitasking
2. http://en.wikipedia.org/wiki/Coroutine
3. http://en.wikipedia.org/wiki/Actor_model
4. http://en.wikipedia.org/wiki/Continuation
5. http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf
6. http://www.scala-lang.org/sites/default/files/odersky/jmlc06.pdf
7. http://www.malhar.net/sriram/kilim/kilim_ecoop08.pdf
8. http://lamp.epfl.ch/~phaller/doc/ScalaActors.pdf