初识coroutine

什么是coroutine?

coroutine是一种古老的计算模式,中文翻译叫做“协程”,是一种多线程编程思路,早在1969年被提出。coroutine本质上是一种轻量级的thread,它的开销会比使用thread少很多。多个coroutine可以按照次序在一个thread里面执行,一个coroutine如果处于block状态,可以交出执行权,让其他的coroutine继续执行。coroutine和thread最大的区别在于,一个coroutine是显式的将执行权交给另一个coroutine,而thread是通过操作系统的调度器来共享CPU资源。下图是两个coroutine之间的协作过程。



coroutine 怎么用?

以Lua语言为例,介绍coroutine的创建,运行,和终止。

  • 创建:

创建一个 coroutine 需要调用一次 coroutine.create 。它只接收单个参数,这个参数是 coroutine 的主函数。 create 函数仅仅创建一个新的 coroutine 然后返回它的控制器(一个类型为 thread 的对象);它并不会启动 coroutine 的运行。

  • 运行:

当Coroutine被第一次调用到的时候,它将从起始处开始执行,一旦遇到具有yield语义的语句的时候,就是返回给另外一个Coroutine或者调用者,而在接下来被调用的时候,就从yield语句下面的一条语句继续执行直到遇到新的yield语句或者Coroutine的结束。

  • 终止:

coroutine 可以通过两种方式来终止运行: 一种是正常退出,指它的主函数返回(最后一条指令被运行后,无论有没有显式的返回指令); 另一种是非正常退出,它发生在未保护的错误发生的时候。第一种情况中, coroutine.resume 返回 true ,接下来会跟着 coroutine 主函数的一系列返回值。第二种发生错误的情况下, coroutine.resume 返回 false ,紧接着是一条错误信息。

说得很抽象,下面看一段Lua代码:

     function foo (a)
       print("foo", a)
       return coroutine.yield(2*a)
     end
     
     co = coroutine.create(function (a,b)
           print("co-body", a, b)
           local r = foo(a+1)
           print("co-body", r)
           local r, s = coroutine.yield(a+b, a-b)
           print("co-body", r, s)
           return b, "end"
     end)
            
     print("main", coroutine.resume(co, 1, 10))
     print("main", coroutine.resume(co, "r"))
     print("main", coroutine.resume(co, "x", "y"))
     print("main", coroutine.resume(co, "x", "y"))

代码中定义了一个函数foo,然后调用coroutine.create创建了一个coroutine,它的运行路线到end处结束。接下来的四个print是主线程的代码,线程从这里开始执行。第一次调用coroutine.resume,coroutine 从主函数的第一行(即print("co-body", a, b))开始运行。在调用foo函数的时候,遇到一个coroutine.yield语句,控制权交还给主线程,主线程执行第二个print中的coroutine.resume再次将控制权交给coroutine,这次coroutine从foo函数之后的一条语句开始运行,以此类推。

于是当你运行以上程序,将得到如下输出结果:

co-body 1       10
foo     2           
main    true    4     
co-body r      
main    true    11      -9     
co-body x       y     
main    true    10      
end      
main    false   cannot resume dead coroutine

coroutine性能如何?

coroutine的编程思路类似Java中的wait/notify。于是有以下对比程序:

lua code

co = coroutine.create(function(loops)
    for i = 1, loops do
        coroutine.yield()
    end
end)

local x = os.clock()
local loops = 100 * 1000 * 1000
coroutine.resume(co, loops)
for i = 1, loops do
    coroutine.resume(co)
end
print(string.format("elapsed time: %.2f\n", os.clock() - x))

java code

public class TestWait {
    public static void main(String[] args) {
        WaitClass wc = new WaitClass();
        wc.start();
        int loops = 100 * 1000 * 1000;
        long t1 = System.currentTimeMillis();
        for (int i = 0; i < loops; i++) {
            synchronized (wc) {
                wc.notify();
            }
        }
        long t2 = System.currentTimeMillis();
        System.out.println("elapsed time: " + (t2 - t1) / 1000l);
    }
}

class WaitClass extends Thread {
    public void run() {
        while (true) {
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果如下:

Lua elapsed time: 53.36
Java elapsed time: 51

CPU占用

运行环境:4core XEON

Lua 1CPU 100%, 其他CPU0%,total 25% (其中CPU sys 0%)

Java 2个CPU 40%-50%, 其他CPU 0%,total 25% (其中CPU sys 5%-10%)

从结果看,coroutine只利用了一个CPU, 因为它实际上是一个串行运算,这也是coroutine不会有传统并行计算的访问冲突的原因。

Java利用了2个CPU, 各占用了50%的CPU时间运行和50%的时间等待,和设计也一致。另外Java用了5-10%的sys CPU时间用于线程context switch。

其实,Java为了调用wait/notify,使用了同步锁,但性能上不比coroutine差。既然这样,基于负载均衡的考虑,coroutine没有任何优势。那么,为什么要用coroutine呢?


为什么要用coroutine?

因为coroutine有它的优点:

•每个coroutine有自己私有的stack及局部变量。
•同一时间只有一个coroutine在执行,无需对全局变量加锁。
•顺序可控,完全由程序控制执行的顺序。而通常的多线程一旦启动,它的运行时序是没法预测的,因此通常会给测试所有的情况带来困难。

其典型的应用场景如下:

•状态机。
•异步IO操作:异步IO操作通常是发起一个IO请求,由操作系统完成以后回调指定的方法或者使用其它方式通知。

•高并发网络服务器,高并发服务器除了要处理场景一的情况外,可能还要结合场景二,多线程方案有时候完全不能接受,更多的是基于事件、异步IO或者是混合事件和多线程的模型。

•客户端并发应用

 Bibliography

         1.lua手册:http://manual.luaer.cn/2.11.html

         2.后端技术博客:http://timyang.blog.51cto.com/1539170/307673




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值