当前位置: 首页 > 工具软件 > 协同放置 > 使用案例 >

LUA协同程序

章烨烨
2023-12-01

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()
 类似资料: