Lua中的协同程序——coroutine,lua协同coroutine


 

Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换。不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时刻只能有一个协程在运行。并且Lua中的协程无法在外部将其停止,而且有可能导致程序阻塞。

 

协同程序基础(coroutine)

 

三个状态:suspended(挂起,协同刚创建完成时或者yield之后)、running(运行)、dead(函数走完后的状态,这时候不能再重新resume)。

 

coroutine.create(arg):根据一个函数创建一个协同程序,参数为一个函数

 

coroutine.resume(co):使协同从挂起变为运行(1)激活coroutine,也就是让协程函数开始运行;(2)唤醒yield,使挂起的协同接着上次的地方继续运行。该函数可以传入参数

 

coroutine.status(co):查看协同状态

 

coroutine.yield():使正在运行的协同挂起,可以传入参数

 

coroutine.running: 返回当前的协程,如果它被主线程调用的话,返回nil

 

如何理解上面提到的每一个协程有自己的堆栈,自己的局部变量,看下面这个例子:

 
coroutineFunc = function (a, b) 
    for i = 1, 10 do
        print(i, a, b)
        coroutine.yield()
    end
end

co2 = coroutine.create(coroutineFunc)        --创建协同程序co2
coroutine.resume(co2, 100, 200)                -- 1 100 200 开启协同,传入参数用于初始化
coroutine.resume(co2)                        -- 2 100 200 
coroutine.resume(co2, 500, 600)                -- 3 100 200 继续协同,传入参数无效

co3 = coroutine.create(coroutineFunc)        --创建协同程序co3
coroutine.resume(co3, 300, 400)                -- 1 300 400 开启协同,传入参数用于初始化
coroutine.resume(co3)                        -- 2 300 400 
coroutine.resume(co3)                        -- 3 300 400 

 

例子中,分别创建了两个协程co2和co3,可以看到,启动co2和co3时,都是从1开始循环,并且参数也是相互独立的,由此就可以看出,co2和co3是相互独立的两个协程。

 

Lua中协同的强大能力,还在于通过resume-yield来交换数据,个人的理解:

 

(1)resume和yield可以通过传参的形式给彼此返回数据,即其中一方的参数是另一方返回值,resume和yield参数上的区别在于resume的第一个参数,yield不会接收;yield在给resume返回参数时,除了参数列表中返回的值外,还会隐式地返回一个bool值,表明协程是否已经dead,因此,resume在接收yield的返回结果时,需要显示地接收该bool值;

 

(2)resume可以理解为函数的调用,yield可以理解为一个return,返回的值一个bool值加上参数列表的值;

 

(3)协程的生命周期等于函数的生命周期,即函数返回时,协程的生命周期也就结束了,这就是为什么可以把yield理解为一个return,但是要注意,yield的return和函数的return的区别,yield的return只是把协程挂起(有点像中断,在中断发生时,会对现场进行保护,当中断返回时,又从之前中断的地方开始执行),而函数的return,则表明函数的生命周期已经结束,即协程的生命周期的结束,函数的return的值,也会传给resume;

 

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
foo 2
main true 4
co-body r
main true 11, -9
co-body x y
main false 10 end
main false cannot resume dead coroutine

 

(4)在启动coroutine的时候,resume的参数是传给主程序(就是创建协程时注册的Lua function)的;在唤醒yield的时候,参数是传递给yield的。

 

co = coroutine.create(function (a, b) print("co", a, b, coroutine.yield()) end)
coroutine.resume(co, 1, 2)        --没输出结果,注意两个数字参数是传递给函数的
coroutine.resume(co, 3, 4, 5)        --co 1 2 3 4 5,这里的两个数字参数由resume传递给yield 

 

Lua的协同称为不对称协同(asymmetric coroutines),指“挂起一个正在执行的协同函数”与“使一个被挂起的协同再次执行的函数”是不同的,有些语言提供对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换”。

 

注意:resume运行在保护模式下,因此,如果协同程序内部存在错误,Lua并不会抛出错误,而是将错误返回给resume函数。

 

下面我们来看一下如何利用Coroutine来解决生产者——消费者问题的简单实现:

 
produceFunc = function()
    while true do
        local value = io.read()
        print("produce: ", value)
        coroutine.yield(value)        --返回生产的值
    end
end

consumer = function(p)
    while true do
        local status, value = coroutine.resume(p);        --唤醒生产者进行生产
        print("consume: ", value)
    end
end

--消费者驱动的设计,也就是消费者需要产品时找生产者请求,生产者完成生产后提供给消费者
producer = coroutine.create(produceFunc)
consumer(producer)

 

这是一种消费者驱动的设计,可以看到resume操作的结果是等待一个yield的返回,这很像普通的函数调用,有木有。我们还可以在生产消费环节之间加入一个中间处理的环节(过滤器):

 

produceFunc = function()     while true do         local value = io.read()    if type(tonumber(value)) ~= "number" then     print("must be a number")     return false, nil    end         print("produce: ", value)         coroutine.yield(value)        --返回生产的值     end end

 

filteFunc = function(p)     while true do         local status, value = coroutine.resume(p);    if not status then     return false, nil    end         value = value *100            --放大一百倍         coroutine.yield(value)     end end

 

consumer = function(f, p)     while true do         local status, value = coroutine.resume(f, p);        --唤醒生产者进行生产    if not status then     break    end         print("consume: ", value)     end end

 

--消费者驱动的设计,也就是消费者需要产品时找生产者请求,生产者完成生产后提供给消费者 producer = coroutine.create(produceFunc) filter = coroutine.create(filteFunc) consumer(filter, producer)

 

可以看到,我们在中间过滤器中将生产出的值放大了一百倍。

通过这个例子应该很容易理解coroutine中如何利用resume-yield调用来进行值传递了,他们像“调用函数——返回值”一样的工作,也就是说resume像函数调用一样使用,yield像return语句一样使用。coroutine的灵活性也体现在这种通过resume-yield的值传递上。

 

参考

http://www.cnblogs.com/sifenkesi/p/3824321.html

http://www.kankanews.com/ICkengine/archives/92940.shtml

 

 

(全文完)

相关内容

    暂无相关文章