coroutine
1. 什么是协同(coroutine)?
协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,但是和其他协同程序共享全局变量等很多信息。
线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协作程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
协同和顺序执行的区别?任一制定时刻只能运行一个协同程序,这个和顺序执行区别在哪?关键在于yield函数。如果顺序执行的时候进行耗费cpu时间或者一直等待某个资源的时候,程序将卡在这个地方不能前进。协同程序的出现就是可以使等待资源的线程让出资源,进行下一个协同程序的操作。yield可以在执行出错的时候挂起,下次恢复的时候再进行操作。
2. 协同(coroutine)的几个状态
挂起态:创建一个协同程序时他开始的状态为挂起态,函数coroutine.yield可以使程序由运行态变为挂起状态
运行态:函数coroutine.resume可以使程序由挂起状态变为运行态
停止态:协同程序结束,进入停止态
3. coroutine.resume
resume可以给协同程序传参数,并将挂起态程序恢复为运行态
coroutine.co = coroutine.create(function (a,b,c)
print("co", a, b, c)
end)
coroutine.resume(co, 1, 2, 3) --> co 1 2 3resume
coroutine.resume直到线程结束或者遇到coroutine.yield时返回。
(1) coroutine.resume的参数:线程函数第一次运行,参数作为线程函数参数;如果yield没有显式返回参数,则coroutine.resume的参数作为yield的额外参数返回。
(2)如果是挂起状态(一开始也是挂起状态),继续运行resume函数返回true;如果线程已经停止或者遇到其他错误,resume函数返回false及错误信息。
(3)线程结束则线程主函数的返回值作为coroutine.resume的附加返回值。
这点特性很微妙,可以看出coroutine.resume其实是个阻塞函数,阻塞等待协同程序完成或者yield退出。可以把协同程序当成一个等待对象,对象等待返回则coroutine.resume返回。在coroutine.resume调用的地方阻塞调用线程,这个特性要记住!
4. coroutine.yield
yield可以返回额外参数,或者挂起协同程序
co = coroutine.create(function (a,b)
coroutine.yield(a + b, a - b)
end)
print(coroutine.resume(co, 20, 10)) --> true 30 10
co = coroutine.create (function ()
print("co", coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co, 4, 5) --> co 4 5
使用函数yield可以使程序挂起并返回状态给resume,当我们激活被挂起的程序时,yield返回(这里的返回是说从阻塞状态返回)并继续程序的执行直到再次遇到yield或者程序结束
5. 对称协同和不对称协同
对称协同:执行到挂起之间状态转换的函数是相同的
不对称协同:挂起一个正在执行的协同的函数与使一个被挂起的协同再次执行的函数是不同的(resum和yield)
6. 消费者驱动的生产者-消费者模型
当消费者需要值时他唤起生产者生产值,生产者生产值后停止直到消费者再次请求。我们称这种设计为消费者驱动的设计。平常多见的生产者-消费者模型,是产品驱动的设计,生产者不断生产产品,消费者用临界区保护取产品消费
协同为解决这种问题提供了理想的方法,因为调用者与被调用者之间的resume-yield关系会不断颠倒。当一个协同调用yield时并不会进入一个新的函数,取而代之的是返回一个未决的resume的调用。相似的,调用resume时也不会开始一个新的函数而是返回yield的调用。这种性质正是我们所需要的,与使得send-receive协同工作的方式是一致的:receive唤醒生产者生产新值,send把产生的值送给消费者消费。
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end
function send (x)
coroutine.yield(x)
end
function producer ()
return coroutine.create(function ()
while true do
local x = io.read() -- produce new value
send(x)
end
end)
end
function filter (prod)
return coroutine.create(function ()
local line = 1
while true do
local x = receive(prod) -- get new value
x = string.format("%5d %s", line, x)
send(x) -- send it to consumer
line = line + 1
end
end)
end
coroutine.resumefunction consumer (filter)
while true do
local x = receive(filter) -- get new value
io.write(x, "\n") -- consume new value
end
end
p = producer()
f = filter(p)
consumer(f)
看完上面这个例子你可能很自然的想到UNIX的管道,协同是一种非抢占式的多线程。管道的方式下,每一个任务在独立的进程中运行,而协同方式下,每个任务运行在独立的协同代码中。管道在读(consumer)与写(producer)之间提供了一个缓冲,因此两者相关的的速度没有什么限制,在上下文管道中这是非常重要的,因为在进程间的切换代价是很高的。协同模式下,任务间的切换代价较小,与函数调用相当,因此读写可以很好的协同处理。
http://my.oschina.net/wangxuanyihaha/blog/186401
http://blog.csdn.net/trojanpizza/article/details/6613465