LUA数据结构(二)

ronald1年前职场3540

Lua数据结构

thread

    Lua中,最主要的线程是协程,它和线程差不多,拥有独立的栈、局部变量和指令指针;和线程区别在于线程可以同时运行多个,而协程同一时刻只能有一个运行

    Lua协程接口都放在table coroutine里面,主要包括以下几个:

    • coroutine.create,创建一个协程

    • coroutine.wrap

    • coroutine.resume,重启一个协程

    • coroutine.yield,挂起一个协程

    • coroutine.status,查看协程状态

    • coroutine.running,返回当前运行的协程

create和wrap

    coroutine.create和coroutine.wrap都是用来创建协程的,区别在于:

    (1)create接收一个方法为参数,这个方法即协程的主流程,返回值可以认为是一个coroutine对象,通过resume接口唤醒这个协程;

    (2)wrap接收一个方法为参数,返回值是一个函数,每次调用相当于将协程拉起

    coroutine.create用法示例:

function func_co1()
    local i=0;
    while true do
        print("co1",i)
        i=i+1
        if i>10 then
            return
        end
        if i%3==0 then
            i=i+1
            coroutine.resume(co2)
        end
    end
end

function func_co2()
    local i=0;
    while true do
        print("co2",i)
        i=i+1
        if i>10 then
            return
        end
        if i%3==0 then
            i=i+1
            coroutine.yield(co2)
        end

    end
end

co1 = coroutine.create(func_co1)
co2 = coroutine.create(func_co2)
coroutine.resume(co1)

    本例中:

        1)通过接口coroutine.create创建了协程co1和co2,并通过resume接口拉起;

        2)create返回的co1和co2是thread类型;

        3)Lua的协程中的局部变量(这里的local i)是协程的私有变量,不会互相影响;但是全局变量是所有协程公用的,使用时候需要注意其中的同步问题;

        4)在Lua中,同一时刻只有一个协程运行,即在协程co1中拉起co2,则co1停止执行,将执行权限交给co2,直到co2将执行权限让出,co1才能继续执行。

    coroutine.wrap用法示例:

function  co_wrapfunc_1()
    for i=0,10 do
        print("co_wrap_1:",i)
        if i%3==0 then
            co_wrap_2("aaa","bbb","ccc")
        end
    end
end

function  co_wrapfunc_2(...)
    for i=0,10 do
        print("co_wrap_2:",i)
        if i%3==0 then
            ret = select(2,...)
            print(ret)
            coroutine.yield(coroutine.running())
        end
    end
end

co_wrap_1=coroutine.wrap(co_wrapfunc_1)
co_wrap_2=coroutine.wrap(co_wrapfunc_2)
co_wrap_1()

    本例中:

        1)coroutine.wrap返回值是一个function类型,每次唤醒这个协程通过类似函数调用的形式即可;

        2)拉起协程类似函数调用的格式,支持参数传递,协程内也可以获取到这个参数,作为唤醒协程后执行的一些输入;

        3)协程被重新拉起时,从上次挂起的位置后一行代码继续往后执行,本例中,对于协程co_wrap_2,重新拉起后,在yield下一句重新开始执行;对于协程co_wrap_1,在拉起协程2的后一句开始执行;

        4)对于coroutine.wrap创建的协程,返回的不是协程的对象而是被封装的方法,因此在yield出CPU的时候需要通过coroutine.running来获取当前正在运行的协程。


Lua的协程状态

    在介绍resume和yield接口之前,先介绍一下Lua协程的状态,Lua协程分为suspend、running、dead、normal状态,其中:

        1)coroutine.create/coroutine.wrapper,协程创建之后处于suspend状态;

        2)coroutine.resume,当协程挂起之后,调用resume接口,协程转入running状态;

        3)当在协程内调用coroutine.resume,则主调协程进入normal状态,此时不是running状态无法挂起,不是挂起状态也无法resume;需要等被调用协程执行完毕才能返回原协程;

        4)当在协程内调用coroutine.yield,则主调协程进入suspend状态;

        5)协程内函数执行完毕,则协程进入dead状态。


yield和resume

Lua协程的normal状态

    Lua中通过yield和resume进行协程执行权的让出和转移,需要注意的一点是Lua是非对称的协程模型,即在协程中可以拉起协程,但被拉起的协程和原协程之间关系是不对等的,不能被拉起协程的子协程拉起父协程的操作,如下示例:

function func_co1()
    local i=0
    while true do
        print("co1",i)
        i=i+1
        if i>10 then
            print("co1 return")
            return
        end
        if i%3==0 then
            i=i+1
            coroutine.resume(co2)
            coroutine.yield(co1)
        end
    end
end

function func_co2()
    local i=0
    while true do
        print("co2",i)
        i=i+1
        if i>10 then
            print("co2 return")
            return
        end
        if i%3==0 then
            i=i+1
            coroutine.resume(co1)
            coroutine.yield(co2)
        end
    end
end

co1 = coroutine.create(func_co1)
co2 = coroutine.create(func_co2)
coroutine.resume(co1)

    此时协程最终会被卡住,更明显的如下:

image-20220112105243410.png

    co_wrap_1中拉起co_wrap_2之后,co_wrap_1事实上处于normal状态,此时在co_wrap_2中再去拉起co_wrap_1,则认为非挂起状态,协程不能被重新拉起而报错。


yield和resume的参数

    如前所述,子协程通过yield将自己的执行权交还给父协程,有些场景下,父协程想拿到子协程的一部分执行结果作为后续执行的依据,yield函数提供了参数列表,将部分需要返给父协程的信息通过这个列表传递回去,如下:

function func_co1()
    local i=0
    while true do
        print("co1",i)
        i=i+1
        if i>10 then
            return
        end
        if i%3==0 then
            i=i+1
            ret1,ret2,ret3=coroutine.resume(co2)
            print(ret1,ret2,ret3)
        end
    end
end

function func_co2()
    local i=0;
    j=0;
    while true do
        print("co2",i,j)
        i=i+1
        if i>10 then
            return
        end
        if i%3==0 then
            i=i+1
            coroutine.yield("aaaa","bbbbb")
        end
    end
end

co1 = coroutine.create(func_co1)
co2 = coroutine.create(func_co2)
coroutine.resume(co1)

    在本例中:

        1)协程co1通过resume接口拉起协程co2,协程co2执行完毕之后通过yield让出执行权,并且将执行的结果通过yield形参列表给出来

        2)协程co1通过resume的返回值获取执行结果,其中第一个返回值为true/false,用来标识协程是否正常执行完毕,后面的参数用于获取子协程的返回值

        3)需要注意的是,当协程已经处于dead状态的时候,不能再启动这个协程,否则会报错,并返回false



参考文件

    构建Lua解释器Part11:Upvalue - 知乎 (zhihu.com)

    scope - In Lua, is there a difference between local functions declared with and without the "local" keyword? - Stack Overflow

    Lua 协同程序(coroutine) | 菜鸟教程 (runoob.com)

    深入理解lua的协程coroutine_papaya的博客-CSDN博客_lua协程

    https://segmentfault.com/a/1190000024528016


相关文章

LUA数据结构(三)

Lua数据结构userdata    Lua官方的介绍:userdata是一种用户自定义数据,用于表示一种由应用程序或者C/C++语言库创建的类型,可以将任意C/C++类型的数据(通常是struct、指针)存储到Lua变量中调用。    在实际应用过程中,C/C++接口调用LuaL_newuserdata就会分配指定大...

    栈(stack)一种操作受限的线性结构,限定仅能在尾部进行插入和删除,能进行插入和删除操作的一端叫做栈顶,而另一端叫做栈底;我们将插入栈的操作叫做入栈,从栈中删除元素的操作叫做出栈,栈是一个先入后出(FILO)的数据结构,即先入栈的元素会后出栈。    栈最常被我们接触的场景就是函数调用了,在进程地址空间中,有...

Lua热更新机制(上)

Lua热更新机制一个Lua热更新demo    Lua在游戏开发中能广泛使用不仅由于其轻量易嵌入的特性,还有一个重要的点是易于热更新,设想在产品线上运营过程中,出现bug需要修复,频繁停机对于产品体验影响大,也影响口碑;所以实际运营我们是希望能尽量避免停止服务进行代码更新的操作,下面先从一段比较简单的代码看Lua的热更新机制:require &qu...

协程-有栈协程(libco)

协程-有栈协程(libco)

libco      还有一个广泛使用的协程库就是libco,libco是被由微信开发并大规模应用的协程库,自2013年起稳定运行于数万台微信后台机器上;具备以下特性:高性能,号称可以调度千万级协程在IO阻塞时,可以自动切换,利用hook技术+epoll事件循环实现阻塞逻辑IO化改造支持嵌套创建既支持共享栈模式也支持独立栈模式提供超时管...

关于LUA(下)

关于LUA(下)

Lua与OOP    Lua是面向过程的语言,不提供面向对象的特性,但是我们可以利用Lua的元表和元方法模拟面向对象的效果。OOP的特性封装    所谓封装,是隐藏对象的属性和细节,仅对外暴露公共的访问方式。本质上分为两层:    1)成员变量和成员方法,提升代码的内聚性,降低模...

Lua的垃圾回收(下)

Lua 5.3版本的垃圾回收Lua垃圾回收源码实现    结合前面描述的垃圾回收的流程,我们参照源码进行逐个的拆解和介绍。    lua的垃圾回收主要都是在接口luaC_step里面完成的,这里面本质上是控制了一个状态机,根据global_State->gcstate进行渐进式的垃圾回收处理。GCSpause&n...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。