lua协程实现(代码片段)

bytemode bytemode     2023-02-04     652

关键词:

协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制;另外,协程也更加轻量级。这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU;而当条件满足时,可以继续执行这个协程。目前在网络服务器领域,使用Lua协程最好的范例就是ngx_lua了

来看看Lua协程内部是如何实现的。

本质上,每个Lua协程其实也是对应一个LuaState指针,所以其实它内部也是一个完整的Lua虚拟机—有完整的Lua堆栈结构,函数调用栈等等等等,绝大部分之前对Lua虚拟机的分析都可以直接套用到Lua协程中。于是,由Lua虚拟机管理着这些隶属于它的协程,当需要暂停当前运行协程的时候,就保存它的运行环境,切换到别的协程继续执行。很简单的实现。

来看看相关的API。

  1. lua_newthread

创建一个Lua协程,最终会调用的API是luaE_newthread,Lua协程在Lua中也是一个独立的Lua类型数据,它的类型是LUA_TTHREAD,创建完毕之后会照例初始化Lua的栈等结构,有一点需要注意的是,调用preinit_state初始化Lua协程的时候,传入的global表指针是来自于Lua虚拟机,换句话说,任何在Lua协程修改的全局变量,也会影响到其他的Lua协程包括Lua虚拟机本身。

  1. 加载一个Lua文件并且执行

对于一般的Lua虚拟机,大可以直接调用luaL_dofile即可,它其实是一个宏:

#define luaL_dofile(L, fn)         (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

展开来也就是当调用luaL_loadfile函数完成对该Lua文件的解析,并且没有错误时,调用lua_pcall函数执行这个Lua脚本。

但是对于Lua协程而言,却不能这么做,需要调用luaL_loadfile然后再调用lua_resume函数。所以两者的区别在于lua_pcall函数和lua_resume函数。来看看lua_resume函数的实现。这个函数做的几件事情:首先查看当前Lua协程的状态对不对,然后修改计数器:

 L->baseCcalls = ++L->nCcalls;

其次调用status = luaD_rawrunprotected(L, resume, L->top – nargs);,可以看到这个保护Lua函数堆栈的调用luaD_rawrunprotected最终调用了函数resume:

static void resume (lua_State *L, void *ud) 
  StkId firstArg = cast(StkId, ud);
  CallInfo *ci = L->ci;
  if (L->status == 0)   /* start coroutine? */
    lua_assert(ci == L->base_ci && firstArg > L->base);
    if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
      return;
  
  else   /* resuming from previous yield */
    lua_assert(L->status == LUA_YIELD);
    L->status = 0;
    if (!f_isLua(ci))   /* `common‘ yield? */
      /* finish interrupted execution of `OP_CALL‘ */
      lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
                 GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
      if (luaD_poscall(L, firstArg))  /* complete it... */
        L->top = L->ci->top;  /* and correct top if not multiple results */
    
    else  /* yielded inside a hook: just continue its execution */
      L->base = L->ci->base;
  
  luaV_execute(L, cast_int(L->ci - L->base_ci));

这个函数将执行Lua代码的流程划分成了几个阶段,如果调用

luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA

那么说明这次调用返回的结果小于0,可以跟进luaD_precall函数看看什么情况下会出现这样的情况:

    n = (*curr_func(L)->c.f)(L);  /* do the actual call */
    lua_lock(L);
    if (n < 0)  /* yielding? */
      return PCRYIELD;
    else 
      luaD_poscall(L, L->top - n);
      return PCRC;
    

继续回到resume函数中,如果之前该Lua协程的状态是YIELD,那么说明之前被中断了,则调用luaD_poscall完成这个函数的调用。
然后紧跟着调用luaV_execute继续Lua虚拟机的继续执行。

可以看到,resume函数做的事情其实有那么几件:

  1. 如果调用C函数时被YIELD了,则直接返回
  2. 如果之前被YIELD了,则调用luaD_poscall完成这个函数的执行,接着调用luaV_execute继续Lua虚拟机的执行。

因此,这个函数对于函数执行中可能出现的YIELD,有充分的准备和判断,因此它不像一般的pcall那样,一股脑的往下执行,而是会在出现YIELD的时候保存现场返回,在继续执行的时候恢复现场。
3)同时,由于resume函数是由luaD_rawrunprotected进行保护调用的,即使执行出错,也不会造成整个程序的退出。

这就是Lua协程中,比一般的Lua操作过程做的更多的地方。

最后给出一个Lua协程的例子:
co.lua

print("before")
test("123")
print("after resume")

co.c

 #include 
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"

    static int panic(lua_State *state) 
      printf("PANIC: unprotected error in call to Lua API (%s)
",
              lua_tostring(state, -1));
      return 0;
    

    static int test(lua_State *state) 
      printf("in test
");
      printf("yielding
");
      return lua_yield(state, 0);
    

    int main(int argc, char *argv[]) 
      char *name = NULL;
      name = "co.lua";
      lua_State*  L1 = NULL;
      L1 = lua_open();
      lua_atpanic(L1, panic);
      luaL_openlibs( L1 );

      lua_register(L1, "test", test);
      lua_State*  L = lua_newthread(L1);

      luaL_loadfile(L, name);
      lua_resume(L, 0);
      printf("sleeping
");
      sleep(1);
      lua_resume(L, 0);
      printf("after resume test
");

      return 0;
    

你可以使用coroutine.create来创建协程,协程有三种状态:挂起,运行,停止。创建后是挂起状态,即不自动运行。status函数可以查看当前状态。协程真正强大的地方在于他可以通过yield函数将一段正在运行的代码挂起。

lua的resume-yield可以互相交换数据

co = coroutine.create(function (a, b)
     coroutine.yield(a+b, a-b)
end)
print(coroutine.resume(co, 3, 8))



python相当于lua协程?(代码片段)

...直在Lua从事积木游戏。核心游戏代码在主游戏循环中使用协程,以等待诸如按下输入或计时器完成之类的事件。我正在考虑将其切换到Python,以使其更加可移植,但我不知道如何正确使用async和await,以等同于Lua的协程。以下代... 查看详情

在lua中使用异步io的思考(代码片段)

LUA协程的介绍lua有一套非常高效的协程机制,这一套实现非常轻量级,虽然简单意味着高效,然而它并不是真正意义上的对称式协程.lua中使用coroutine.create来创建一个协程,使用coroutine.resume来执行协程。使用coroutine.yield来让出当前正... 查看详情

lua中的协同程序coroutine(代码片段)

  Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换。不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时刻只能有一个协程在运... 查看详情

lua:写了个基于协程的task调度库(代码片段)

写了一个(不完整的)基于协程的task调度库 samplecode如下my_spawn(function()print(‘f:1‘)localt1=my_spawn(function()print(‘f:3‘)task_yield_to_be_schedule()print(‘f:4‘)end)--task_yield_to_be_schedule()my_wait_task(t1)pr 查看详情

lua脚本延迟10秒怎么写

...uot;socket")socket.sleep(10)--休眠10秒使用coroutine.yield()。在协程中使用yield函数可以实现暂停程序执行一段时间的效果,从而实现延迟。例如,下面的代码使用协程实现了延迟10秒的效果:luaCopycodefunctiondelay()localstart=os.time()whileos.ti... 查看详情

nginx框架之lua拓展(代码片段)

...ql模块 OpenRestyhttp模块Lua模板渲染器使用 Nginx非阻塞与Lua协程的绝配Lua协程 查看详情

lua-async协程的高级用法(代码片段)

Lua-Async这是一个基于协程的异步调用库,该库的设计思路类似JavaScript的Promise,但相比Promise,它有更多的灵活性.--引入AsynclocalAsync=require("Async")--创建Async--可传递参数到接下来的调用中Async.New(...)--注册异步调用Async.New(...):Ok(fu... 查看详情

golnag中的并发编程-协程的实现(代码片段)

协程轻量级,通过goroutine实现协程使用方法:go+函数名:启动一个协程执行函数体packagemainimport("fmt""time")functestRoutine()fmt.Println("thisisoneroutine!!!")funcmain()//执行协程gotestRoutine()time.Sleep(1)//协程与线程的关系 查看详情

lua学习之协同程序

...载:http://www.cnblogs.com/sifenkesi/p/3824321.html    Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换。不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的... 查看详情

探索golang协程实现——从v1.0开始(代码片段)

李乐问题引入  提起协程,你可能会说,不就gofunc吗,我分分钟就能创建上万个协程。可是协程到底是什么呢?都说协程是用户态线程,这里的用户态是什么意思?都说协程比线程更轻量,协程轻量在哪里呢?  本文主要为... 查看详情

kotlincoroutines协程实现原理全解析(代码片段)

HelloCoroutinesWorldpackagecoroutines.demoimportkotlinx.coroutines.coroutineScopeimportkotlinx.coroutines.delayimportkotlinx.coroutines.launchsuspendfunmain()=coroutineScopelaunchdelay(1000)prin 查看详情

聊一聊unity协程背后的实现原理(代码片段)

Unity开发不可避免的要用到协程(Coroutine),协程同步代码做异步任务的特性使程序员摆脱了曾经异步操作加回调的编码方式,使代码逻辑更加连贯易读。然而在惊讶于协程的好用与神奇的同时,因为不清楚协程背后的... 查看详情

(转)通过汇编语言实现c协程(代码片段)

转自:http://www.cnblogs.com/sniperHW/archive/2012/06/19/2554574.html协程的概念就不介绍了,不清楚的同学可以自己google,windows和unixlike系统本身就提供了协程的支持,windows下叫fiber,unixlike系统下叫ucontext.在这里重复制造轮子,一是为了更清楚了... 查看详情

lua中的协同程序coroutine

 Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换。不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时刻只能有一个协程在运行... 查看详情

android防重复点击(kotlin协程实现和handler实现)(代码片段)

文章目录Kotlin协程实现调用Handler+Runnable实现调用基于系统时间实现调用setTag/getTag可能引发异常考虑了,直接绑定View#setOnClickListener实现函数;或传入View.OnClickListener实例。Kotlin协程实现调用mBind.btnJaClickA.debounceClick(lifec... 查看详情

基于windowsfiber的协程(coroutine)实现(代码片段)

一个非常简单,但是实用的协程实现,使用Windows的*Fiber函数族(linux可以稍微改一下用*context函数族)。fco.h#ifndef_MSC_VER#error"thisfastcoroutinelibraryonlysupportsMSVCbuildingchain"#endif#include<Windows.h>#include<cst 查看详情

kotlin协程协程底层实现③(结构化并发|mainscope作用域|取消协程作用域|activity实现coroutinescope协程作用域接口)(代码片段)

文章目录一、MainScope协程作用域二、取消MainScope协程作用域三、Activity实现CoroutineScope协程作用域接口常见的CoroutineScope协程作用域:GlobalScope:该作用域是进程级别的,与应用进程同级,即使Activity被销毁,协程任务也可以继续执行;MainS... 查看详情

kotlin协程协程底层实现③(结构化并发|mainscope作用域|取消协程作用域|activity实现coroutinescope协程作用域接口)(代码片段)

文章目录一、MainScope协程作用域二、取消MainScope协程作用域三、Activity实现CoroutineScope协程作用域接口常见的CoroutineScope协程作用域:GlobalScope:该作用域是进程级别的,与应用进程同级,即使Activity被销毁,协程任务也可以继续执行;MainS... 查看详情