1.协程概述
协同程序与线程差不多,也就是一条执行序列,拥有自己独力的栈,局部变量和指令指针,
同时又与其他协同程序共享全局变量和其他大部分东西。
协程和线程的主要区别在于,多个协程在同一时刻只能运行一个协同程序,运行的协程只会在其显式要求挂起时,才会暂停执行
Lua将所有的协同程序函数放置在一个名为“coroutine”的table中
函数create用于创建新的协同程序,只有一个函数参数,返回thread类型的值,参数通常是一个匿名函数。
co = coroutine.create(function() print("hi") end)
print(co) -->thread:0x8071d98
协程的四个状态:挂起(suspended)、运行、死亡、正常。刚创建的协程处于挂起状态,status函数可以检查协程状态
print(coroutine.status(co)) -->suspended
函数resume用于启动或再次启动一个协同程序,将其由挂起态变为运行态
coroutine.resume(co) --> hi
print(coroutine.status(co)) -->dead 使用后便无法返回了
函数yield可以让一个运行中的协程挂起
co = coroutine.create(function()
for i = 1, 10 do
print("co", i)
coroutine.yield()
end
end)
coroutine.resume(co) -->co 1
print(coroutine.status(co)) -->suspended
当一个协同程序A唤醒另一个协同程序B时,协同程序A处于正常状态
协程通过resume-yield来交换数据,resume的返回值中会有yield函数中的参数。在第一次调用resume时,并没有yield在等待它,因此所有传递给resume的
额外参数都将视为协同程序主函数的参数:
co = coroutine.create(function(a,b,c)
print("co", a, b, c)
end)
coroutine.resume(co, 1, 2, 3) -->co 1 2 3
在resume调用返回的内容中,第一个值为true表示没有错误,而后面所有的值都是对应yield传入的参数
co = coroutine.create(function (a,b)
coroutine.yield(a+b, a-b)
end)
print(coroutine.resume(co, 20, 10)) --> true 30 10
与此对应的是,yield返回的额外值就是对应resume传入的参数:
co = coroutine.create(function()
print("co", coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co, 4, 5) --> co 4 5
当一个协同程序结束时,它的主函数所返回的值都将作为对应resume的返回值
co = coroutine.create(function()
return 6,7
end)
print(coroutine.resume(co)) –>true 6 7
经典实例:生产者消费者模型
producer = coroutine.create(
function()
while true do
local x = io.read() --产生新的值
send(x) --发送给消费者
end
end)
function consumer(prod)
while true do
local x = receive(prod) --从生产者接收值
io.write(x, "\n") --消费新的值
end
end
function filter(prod)
return coroutine.create(function()
for line = 1, math.huge do
local x = receive(prod) --获取新值
x = string.format("%5d %s", line, x)
send(x) --将新值发送给消费者
end
end)
end
function receive(prod)
local status, value = coroutine.resume(prod)
return value
end
function send(x)
coroutine.yield(x)
end
f = filter(producer)
consumer(f)
2.协程概述协程实现迭代器
先写一个产生排列组合函数
function permgen(a, n)
n = n or #a --默认n为a的大小
if n <= 1 then --还需要改变吗
printResult(a)
else
for i = 1, n do
a[n],a[i] = a[i],a[n] --将第i个元素放到数组末尾
permgen(a, n-1) --生成其余元素的排列
a[n],a[i] = a[i],a[n] --恢复第i个元素
end
end
end
定义打印函数
function printResult(a)
for i = 1, #a do
io.write(a[i], "")
end
io.write("\n")
end
转化为迭代器
function permgen(a,n)
n = n or #a
if n <= 1 then
coroutine.yield(a)
else
同上
定义协程生产函数,并创建迭代器函数,迭代器只是简单的唤醒协同程序,产生下一种排列
function permutations(a)
local co = coroutine.create(function() permgen(a) end)
return function() --迭代器
local code,res = coroutine.resume(co)
return res
end
end
for p in permutations{"a", "b", "c"} do
printResult(p)
end
Lua中专门提供了一个函数coroutine.wrap来完成这个功能。类似与create,wrap创建了一个新的协同程序。
但wrap返回一个函数,每调用此函数,可唤醒一次协同程序。出错时则会引发错误。
可以这么写permutations
function permutations(a)
return coroutine.wrap(function() permgen(a)end)
end
3.协程阻塞解决
协同程序是非抢先式的,无法从外部停止,只要有一个线程阻塞,整个程序都会停止下来
下面介绍解决方法:
例:通过http下载几个文件,使用的是Diegi Nehab开发的LuaSocket
require "socket" --加载lua库
host = "www.w3.org"
file = "/TR/REC-html32.html"
接下来打开一个tcp连接,连接到该站点的80端口
c = assert(socket.connect(host,80))
返回连接对象,发送文件请求
c:send("GET"..file.."HTTP/1.0\r\n\r\n")
按1K的字节块来接收文件,并将每块写到标准输出:
while true do
local s,status,partial = c:receive(2^10)
io.write(s or partial)
if status == "closed"then break end
end
在正常情况下receive函数会返回一个字符串。若发生错误,则会返回nil,并且附加错误代码(status)及出错前读取到的代码(partial)
当主机关闭连接时,就将其余接收到的内容打印出来。下载完文件关闭连接
c:close()
协程提供了一种简便的并发方式,同时下载多个文件(本例只计算并打印文件的大小):
function download(host, file)
local c = assert(socket.connect(host,80)
local count = 0
c:seng("GET"..file.."HTTP/1.0\r\n\r\n")
while true do
local s,status,partial = receive(c)
count = count + #(s or partial)
if status == "closed" then break end
end
c:close()
print(file, count)
end
receive防阻塞的代码如下
function receive (connection)
connection:settimeout(0) --使receive调用不会阻塞
local s,status,partial = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection)
end
return s or partial, status
end
调度程序:table threads为调度程序保存着所有正在运行中的线程。函数get确保每个下载任务都在一个独力的线程中执行
调度程序遍历所有的线程,逐个唤醒它们的执行,线程完成任务时,将其从列表删除
threads = {} --用于记录所有正在运行的线程
function get(host, file)
--创建协同程序
local co = coroutine.create(function()
download(host, file)
end)
--将其插入记录表中
table.insert(threads, co)
end
function dispatch()
local i = 1
while true do
if threads[i] == nil then --还有线程吗
if threads[1] == nil then break end --列表是否为空
i = 1 --重新开始循环
end
local status,res = coroutine.resume(threads[i])
if not res then --线程是否已经完成了任务
table.remove(threads, i)
else
i = i+1
end
end
主程序如下:
host = "www.w3.org"
get(host, "/TR/htm1401/html40.txt")
get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host, "/TR/REC-html32.html.html")
get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")
dispatch()