协同程序:拥有自己独立的栈、局部变量和指令指针,又与其他协同程序共享全局变量和其他大部分东西。与线程有些类似,区别是:一个具有多个协同程序的程序在任意时刻只能运行一个协同程序。
挂起态:创建一个协同程序时他开始的状态为挂起态,函数coroutine.yield可以使程序由运行态变为挂起状态,之后还可以再恢复其运行。
运行态:函数coroutine.resume可以使程序由挂起状态变为运行态,resume在保护模式中运行。
死亡态:协同程序的运行态结束后,协同程序结束,进入停止态。
正常态:当一个协同程序A唤醒另一个协同程序B时,A就处于一种特殊状态——既不是挂机状态(A不能继续执行),也不是运行状态(B在运行)。
local co = coroutine.create(function(name)
print(name)
coroutine.yield("yield param")
end)
local result, msg = coroutine.resume(co, "resume param");--result为true则代表成功执行
print("msg:" .. msg);
--输出
--resume param
--msg:yield param
yield函数传递了一个参数进去。而这个函数将作为resume的第二个返回值,前提是,resume函数的第一个返回值是true。
local co = coroutine.create(function(name)
for i = 1, 2, 1 do
print(name)
print("co:" .. coroutine.yield("yield param"));
end
end);
for i = 1, 2, 1 do
print("=========第" .. i .. "次执行:")
local result, msg = coroutine.resume(co, "resume param");
print("msg:" .. msg);
end
--输出
--[[
-=========第1次执行:
resume param
msg:yield param
=========第2次执行:
co:resume param
resume param
msg:yield param
--]]
第一次执行的时候,协同程序第一次被挂起,所以yield的返回要等待第二次resume被调用时才能得到。
于是,第二次调用resume时,首先就得到了上一次yield的返回值了,这个返回值正是resume的第二个参数。
没错,resume的第二个返回值是yield的参数,而yield的返回值,是resume的第二个参数。
再简单一些,resume的返回值是yield的参数,yield的返回值是resume的参数。
同时,resume的第二个参数也能传递给协同程序的函数。
local co = coroutine.create(function(name)
for i = 1, 2, 1 do
print(name)
print("co:" .. coroutine.yield("yield param"));
end
return "协同程序函数结束!"
end);
for i = 1, 3, 1 do
print("=========第" .. i .. "次执行:")
local result, msg = coroutine.resume(co, "resume param");
print("msg:" .. msg);
end
--输出
--[[
=========第1次执行:
resume param
msg:yield param
=========第2次执行:
co:resume param
resume param
msg:yield param
=========第3次执行:
co:resume param
msg:协同程序函数结束!
--]]
前两次的执行结果没变,第三次就有点特别。
第三次执行resume时,首先就得到了第二次yield的返回,输出“co:resume param”。
注意一下,这里是不会继续执行print(name);这句代码的,也就是说,整个协同程序函数的for循环是不会被执行的。
function producer()
return coroutine.create(function()
while true do
local x=io.read()
coroutine.yield(x)
end
end)
end
function filter(prod)
return coroutine.create(function()
for line=1,math.huge do
local _, x=coroutine.resume(prod)
x=string.format("%5d %s",line,x)
coroutine.yield(x)
end
end)
end
function consumer(prod)
while true do
local _, x=coroutine.resume(prod)
io.write(x,"\n")
end
end
p=producer()
f=filter(p)
consumer(f)
过滤器介于两者之间。
消费者作为主导函数,consumer函数resum起filter,filter又resume起producer,由producer生产一段数据后yield使filer的resume返回,加工数据,filter又yield使consumer的resume返回。
local socket = require "socket"
local host = "www.lua.org"
local HTTP = "HTTP/1.0\r\nUser-Agent: Wget/1.12 (linux-gnu)\r\nAccept: */*\r\nHost: www.lua.org\r\nConnection: Keep-Alive\r\n\r\n"
function receive(connection)
connection:settimeout(0) --设置为非阻塞
local s,status,partial = connection:receive(2^10)
if status == "timeout" then coroutine.yield(connection) end
return s or partial,status
end
function download(host,file)
local sock = assert(socket.connect(host,80))
local count = 0 --记录接收到的字节数
sock:send("GET " .. file .. HTTP)
repeat
local chunk,status = receive(sock)
count = count + #chunk
until status == "closed"
sock:close()
print(file,count)
end
--保存活跃线程的表
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) --移除list中第i个线程
else
i = i + 1 --检查下一个线程
end
end
end
--[[
同时下载9个文件总共耗时36秒,比串行下载9个文件速度快很多。
但是发现CPU占用率跑到98%。
为了避免这样的情况,可以使用LuaSocket中的select函数(socket.select(recvt, sendt [, timeout]))。
在等待时陷入阻塞状态,若要在当前实现中应用这个函数,只需要修该调度即可:
--]]
function dispatch_new()
local i = 1
local timedout = {} --Recvt 集合
while true do
if threads[i] == nil then --没有线程了
if threads[1] == nil then break end --表是空表
i = 1 --重新开始循环
timedout = {} --遍历完所有线程,开始新一轮的遍历
end
local status,res = coroutine.resume(threads[i]) --唤醒该线程继续下载文件
if not res then --若完成了res就为nil,只有status一个返回值true。否则res为yield传入的参数connection。
table.remove(threads,i) --移除list中第i个线程
else
i = i + 1 --检查下一个线程
timedout[#timedout +1] = res
if #timedout == #threads then --所有线程都阻塞了吗?
socket.select(timedout) --如果线程有数据,就返回
end
end
end
end
local file1 = "/ftp/lua-5.3.3.tar.gz "
local file2 = "/ftp/lua-5.3.2.tar.gz "
local file3 = "/ftp/lua-5.3.1.tar.gz "
local file4 = "/ftp/lua-5.3.0.tar.gz "
local file5 = "/ftp/lua-5.2.4.tar.gz "
local file6 = "/ftp/lua-5.2.3.tar.gz "
local file7 = "/ftp/lua-5.2.2.tar.gz "
local file8 = "/ftp/lua-5.2.1.tar.gz "
local file9 = "/ftp/lua-5.2.0.tar.gz "
get(host,file1)
get(host,file2)
get(host,file3)
get(host,file4)
get(host,file5)
get(host,file6)
get(host,file7)
get(host,file8)
get(host,file9)
--dispatch() --main loop
dispatch_new() --main loop