Lua入门(二)协程基础

协同程序:拥有自己独立的栈、局部变量和指令指针,又与其他协同程序共享全局变量和其他大部分东西。与线程有些类似,区别是:一个具有多个协同程序的程序在任意时刻只能运行一个协同程序。

协程的四种状态

挂起态:创建一个协同程序时他开始的状态为挂起态,函数coroutine.yield可以使程序由运行态变为挂起状态,之后还可以再恢复其运行。
运行态:函数coroutine.resume可以使程序由挂起状态变为运行态,resume在保护模式中运行。
死亡态:协同程序的运行态结束后,协同程序结束,进入停止态。
正常态:当一个协同程序A唤醒另一个协同程序B时,A就处于一种特殊状态——既不是挂机状态(A不能继续执行),也不是运行状态(B在运行)。

resume-yield间的数据返回研究

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值