今天我们先来看下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"))
执行结果:
co-body    1    10        -- 协程co的第7行,此时resume()传入的参数是赋值给了函数的
foo    2            -- 在第8行里面调用了函数foo(),执行到第2行的打印
main    true    4        -- 由于函数foo()的第3行yield()执行后挂起,参数是4,作为第15行的resume()的第二个返回值,最终打印了出来,到此,第15行执行完毕
co-body    r            -- 第16行resume()再次唤醒协程co,接着上次yield()的地方继续执行,参数“r"被赋值给上次yield()的返回值,在第9行打印出来
main    true    11    -9    -- 在第10行yiled()后再次挂起协程co,并返回,此时参数a和b还是第一次resume()时的参数,1,10,所以yield()两个参数分别为11,-9,作为resum()的第二个返回值,最终被打印出来,到此,第16行执行完毕
co-body    x    y        -- 第17行resume()再次唤醒协程co,传入的参数“x”,“y”被赋值给上次的yield()函数的返回值,即赋值给第10行的r,s,在第11行被打印出来
main    true    10    end    -- 协程co在第12行返回,注意此时参数b仍然是第一次resume()时的参数2,值为10,至此协程co执行结束,变为dead状态,最终在第17行打印出来
main    false    cannot resume dead coroutine -- 第18行尝试再次resume()协程co,由于协程co已经为dead状态,所以直接返回并报错

上面这个实例很好的展示了coroutine.yield和coroutine.resume之间的相互作用。协程在生产者和消费者问题上应用也比较广泛,我们来看看下面这个例子。

生产者与消费者:
coConsume = coroutine.create(
    function ()
        while true do
            local stutas, msg = coroutine.resume(coProducer)
            print('receive msg : ', msg)
            coroutine.resume(coProducer, string.len(msg))
        end
    end
)

coProducer = coroutine.create(
    function ()
        while true do
            local msg = io.read()
            local len = coroutine.yield(msg)
            print('he tell me he has recved data len is ', len)
            coroutine.yield()
        end
    end
)
coroutine.resume(coConsume)
运行结果:
hello         --从键盘输入
receive msg :     hello
he tell me he has recved data len is     5

上面这个实例有两个协程,一个是生产协程主要是从屏幕上接收输入的数据,另外一个是消费协程,处理接收到的信息将其打印。

总结:

最后引用知乎上的一段话作为协程学习的一个小结。
在IO密集型的程序中由于IO操作远远小于CPU的操作,所以往往需要CPU去等IO操作。同步IO下系统需要切换线程,让操作系统可以在IO过程中执行其他的东西。这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带来了大量的性能的浪费,尤其是IO密集型的程序。

所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程切换带来性能损失。但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。 而是每次来段数据就要判断 数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。

但是协程可以很好解决这个问题,比如 把一个IO操作 写成一个协程。当触发IO操作的时候就自动让出CPU给其他协程。要知道协程的切换很轻的。协程通过这种对异步IO的封装 既保留了性能也保证了代码的 容易编写和可读性。在高IO密集型的程序下很好,但是高CPU密集型的程序下没啥好处。

03-05 15:55