【Lua进阶系列】协程

                          【Lua进阶系列】协程

   

  大家好,我是Lampard猿奋~~

  欢迎来到Lua进阶系列的博客,今天和大家分享一下lua中关于协程的知识点

(一)什么是协程

    Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它几乎一切资源。

    一个多线程程序可以同时并行运行几个线程,而协程却需要彼此协作地运行,并非真正的多线程。即一个多协程程序在同一时间只能运行一个协程,并且正在执行的协程只会在其显式地要求挂起(suspend)时,它的执行才会暂停。

    Lua中的协同程序有点类似于在等待同一个线程锁的多线程程序。

 

(二)协程&线程的作用:

一开始大家想要同一时间执行那么三五个程序,大家能一块跑一跑,于是就有了并发。

但是一块跑就有问题了。我计算到一半,刚把多次方程解到最后一步,你突然插进来,我的中间状态咋办,我用来储存的内存被你覆盖了咋办?所以跑在一个cpu里面的并发都需要处理上下文切换的问题。进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。

后来一电脑上有了好几个cpu,好咧,大家都别闲着,一人跑一进程。就是所谓的并行

有的时候碰着I/O访问,阻塞了后面所有的计算。空着也是空着,老大就直接把CPU切换到其他进程,让人家先用着。当然除了I\O阻塞,还有时钟阻塞等等。一开始大家都这样弄,后来发现不成,太慢了。为啥呀,一切换进程得反复进入内核,置换掉一大堆状态。进程数一高,大部分系统资源就被进程切换给吃掉了。后来搞出线程的概念,大致意思就是,这个地方阻塞了,但我还有其他地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不用特别麻烦的切换页表、刷新TLB,只要把寄存器刷新一遍就行,能比切换进程开销少点。

比如在进程里面写一个逻辑流调度的东西,碰着i\o我就用非阻塞式的(比如在加载资源时,我们可以做一些初始化场景的逻辑)。那么我们就避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是协程。

 

(三)Lua中协程的调用

    Lua语言中协程相关的所有函数都被放在全局表coroutine中。

(1)coroutine.create()

    我们可以简单的通过coroutine.create()来创建一个协程,该函数只有一个参数即协程要执行的代码的函数(函数体body),然后把创建出来的协程返回给我们。

    举个栗子:

local coA = coroutine.create(function()
    print("hello coroutine")
end)

print(type(coA))    -- 输出字符串thread

(2)coroutine.status()

    一个协程有以下四种状态:既挂起(suspended),运行(running),正常(normal),死亡(dead)。我们可以通过coroutine.status()来输出协程的当前状态。

    当协程被create出来时,它处在挂起的状态,被唤醒之后会处在运行状态,当协程A唤醒协程B时协程A从运行转换成正常状态当唤醒成功之后转化为挂起状态。而当整个协程体执行完毕的时候,协程处于死亡状态。

    我们可以在coroutine表中找到相关定义

 

(3)resume和yeild

resume和yeildr的协作是Lua协程的核心,coroutine.resume()把挂起态的协程唤醒,第一个参数是唤醒的协程,之后的参数传给协程体和作为yeild的返回值。coroutine.yeild()把运行态的协程挂起,参数作为resume的返回值

经典生产者消费者例子:

创建一个生产工厂,让它生产10件产品,每生产一件就把协程挂起,等待客户下一次提交需求的时候才重新resume唤醒

local newProductor

-- 生产者
function productor()
    local i = 0
    while true do
        i = i + 1
        send(i)    -- 将生产的物品发送给消费者
    end
end

-- 消费者
function consumer()
    local i = receive()
    while i <= 10 do
        print("生产第"..i.."件商品")
        i = receive()
    end
end

-- 接受
function receive()
    -- 唤醒程序
    local status, value = coroutine.resume(newProductor)    -- 第一个协程的状态为true时则证明唤醒成功
    return value
end

-- 发送
function send(x)
    coroutine.yield(x)    -- yield的参数作为resume的返回值
end

-- 创建生产工厂
newProductor = coroutine.create(productor)
consumer()

测试结果:

以上的设计称为消费者驱动式的设计,其中生产者是协程,消费者需要使用时才唤醒生产者。同样的我们可以设计消费者为协程,由生产者唤醒的生产者驱动设计。

 

(四)通过协程实现异步I/O

 一般的同步IO(读取文件内容,逆序输出)

local t = {}
local inp = io.input("text.txt")
local out = io.output()

-- 读取文件中的内容
for line in inp:lines() do
    t[#t + 1] = line
end

-- 逆序输出
for i = #t, 1, -1 do
    out:write(t[i], "\n")
end

文件内容&输出结果

 使用异步I/O的方式实现

首先,把读写循环的逻辑抽象出来。使用一个命令队列存放读写的逻辑,若未读取完毕则继续读取,若读取完毕,则进行输出。直至输出完所有的信息之后,则结束逻辑。

local cmdQueue = {}
local lib = {}

-- 读
lib.readLine = function(stream, callback)
    local nextCmd = function()
        callback(stream:read())
    end
    table.insert(cmdQueue, nextCmd)
end

-- 写
lib.writeLine = function(stream, line, callback)
    local nextCmd = function()
        stream:write(line)
        callback()
    end
    table.insert(cmdQueue, nextCmd)
end
 
-- 停止
lib.stop = function()
    table.insert(cmdQueue, "stop")
end

-- 执行
lib.runLoop = function()
    while true do
        local nextCmd = table.remove(cmdQueue, 1)
        if not nextCmd or nextCmd == "stop" then
            break
        end
        nextCmd()
    end
end

return lib

然后,把整个读取的流程编写成一个协程。其中每次进行读写时把协程挂起,当读写完一行之后,则通过回调重新唤醒,从而实现异步I/O。输出的结果是一样的,但若碰到IO阻塞,我们就可以愉快地调用其他协程避免阻塞啦

local lib = require "asyncLib"

-- 创建协程
local run = function(func)
    local coFunc = coroutine.wrap(function()
        func()
        lib.stop()
    end)
    coFunc()
    lib.runLoop()
end

local putline = function(stream, line)
    local co = coroutine.running()
    local callback = function()
        coroutine.resume(co)
    end
    lib.writeLine(stream, line, callback)
    coroutine.yield()
end

local getLine = function(stream)
    local co = coroutine.running()
    local callback = function(line)
        coroutine.resume(co, line)
    end
    lib.readLine(stream, callback)
    local line = coroutine.yield()
    return line
end

-- 调用
run(function()
    local t = {}
    local inp = io.input("text.txt")
    local out = io.output()

    while true do
        local line = getLine(inp)
        if not line then break end
        t[#t + 1] = line
    end

    for i = #t, 1, -1 do
        putline(out, t[i].."\n")
    end
end)

 

以上是学习路上的一点思绪,欢迎大家评论指点~

点赞,关注!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lampard杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值