lua C API(6) - 协程和state

2018-09-03 09:58:47

协程的介绍请参考 lua 协程,这里我们看看如何在 C语言里创建协程并配合 lua 脚本。

1.多个协程其实就是多个独立的栈。

2.声明一个 state,里面自动会生成一个协程,称为主协程,这个主协程不会被垃圾回收 它会跟 state 一起释放比如调用 lua_close。

lua_State* L = luaL_newstate();

3.当然我们可以在此基础上创建另外一个协程,调用如下方法,此时,主协程的栈顶会压入一个新协程。 L,L1属于同一个 state,只是它们拥有自己独立的栈。 L1 刚创建时栈是空的,而 L 的栈顶为 L1 协程。

L1 = lua_newthread(L);

printf("%d\n", lua_gettop(L1)); --> 0,代表栈为空
printf("%s\n", luaL_typename(L, -1)); --> thread

4.注意,除了主协程,其他协程都会被垃圾回收器所监控。 当我们创建新协程,该协程会被压入主协程的栈顶来标记该协程不能被垃圾回收。我们绝不能使用不在主协程栈里的协程。 一些调用 lua api 的动作可能会促使不在主协程栈里的协程被回收。

例子

--main.lua
function foo(x)
    --接收到10,20
    local a,b = coroutine.yield(10,x)
end

function foo1(x)
    foo(x + 1)
    return 3
end
//main.c
#include <stdio.h>
#include <string.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

void main(){
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    if (luaL_loadfile(L, "main.lua") || lua_pcall(L, 0, 0, 0))
		printf("loadfile failed! %s\n", lua_tostring(L, -1));//如果失败,栈顶为错误信息
    
    lua_State *L1 = lua_newthread(L);  //创建新协程
    
    //在协程里调用 foo1 函数
    lua_getglobal(L1, "foo1");
    lua_pushinteger(L1, 20);

    //如果遇到 lua 代码的 yield,那么 i = 1
    //此时,栈里就是 coroutine.yield 里面的参数,10,20
    int i = lua_resume(L1, 1);   //1(LUA_YIELD)
    
    printf("%d\n", lua_gettop(L1)); //-->栈里的参数个数 2

    //栈里的参数值
    printf("%d\n", lua_tointeger(L1, 1)); //--> 10
    printf("%d\n", lua_tointeger(L1, 2)); //--> 20
    
    //重新运行,并从当前栈里传递2个参数(10,20)
    i = lua_resume(L1, 2);  //i=0 代表结束
    
    //此时返回一个结果3
    printf("%d\n", lua_gettop(L1)); //--> 1
    printf("%d\n", lua_tointeger(L1, 1)); //--> 3
}

在标准 lua 里,C 函数里是不能挂起的,只有一种可能,就是在返回的时候比如

return lua_yield(L, nres);

nres 代表多少个值在栈里需要被返回,在 resume 是就会被收到。

但是我们可以通过一些小技巧克服。

int prim_read (lua_State *L) {
    if (nothing_to_read())
        return lua_yield(L, 0);
    lua_pushstring(L, read_some_data());
    return 1;
}
function read ()
    local line

    repeat
        line = prim_read()
    until line

    return line
end

这样,当 lua_yield 被执行,该协程就会挂起,直到遇到下一次 resume,就会从 lua_yield 出开始继续执行。

lua states

当我们每次调用 luaL_newstate,将会创建新的 Lua state。每个 Lua state 之间完全独立。不共享任何数据,也不会相互影响。

所以他们之间想通信,必须通过 c代码介入,C 和 Lua 可以直接交互的只能是字符串和数字,比如

lua_pushstring(L2, lua_tostring(L1, -1));

加载注册所有的标准库

luaL_openlibs(L);

也可以手动加载需要的标准库,从这里我们可以看出,当 package.preload['io'] 为一个函数时,当遇到 require("io") 时,则会调用该函数,并把该函数的返回值返回。 

static void registerlib (lua_State *L, const char *name, lua_CFunction f) {
    lua_getglobal(L, "package");
    lua_getfield(L, -1, "preload"); //把 package.preload 压入栈顶
    lua_pushcfunction(L, f);  //把函数 f 压入栈顶
    
    lua_setfield(L, -2, name); /* package.preload[name] = f 并弹出 f*/
    
    lua_pop(L, 2); /* pop ’package’ and ’preload’ tables */
}

static void openlibs (lua_State *L) {
    lua_cpcall(L, luaopen_base, NULL); /* open basic library */
    lua_cpcall(L, luaopen_package, NULL); /* open package lib. */
    
    //手动注册各个库
    registerlib(L, "io", luaopen_io);
    registerlib(L, "os", luaopen_os);
    registerlib(L, "table", luaopen_table);
    registerlib(L, "string", luaopen_string);
    registerlib(L, "math", luaopen_math);
    registerlib(L, "debug", luaopen_debug);
}


内存管理

在大多数情况下,我们都无需关心和操作内存,因为垃圾回收机制会帮我们处理,当我们销毁一个 state 时,跟它相关的所有内存都会被回收。

但是有时候比如在一些内存比较有限的情况下,我们可以自己控制内存以及垃圾回收机制。

内存分配函数

当我们调用 luaL_newstate 时,创建了一个 state,那么内部就会利用 malloc–realloc–free 来控制内存分配和释放,无需我们操心,但是有时候我们需要手动控制,那么可以利用如下函数

lua_State *lua_newstate (lua_Alloc f, void *ud);

该函数接收2个参数,第一个为内存分配函数,第二个为用户数据。如果利用该函数生成 state,那么所有内存的分配和释放由 f 来控制,即使 lua_State 本身也是由 f 分配,f 函数的定义如下

typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);

这部分内容一般给那些超高级开发人员使用,一般都要熟悉 lua 源码,在这里暂时不研究。


备注:

1.本文只是对模块加载机制做简单的介绍,如果有疑问可以给我留言
2.lua的版本为5.1,运行环境centos7 64位
3.原文地址http://www.freecls.com/a/2712/105


©著作权归作者所有
收藏
推荐阅读
  • lua C API(5) - 用户自定义类型

    除了写C函数来扩展 Lua,我们也可以在 C 里面自定义一些类型。userdatalua_newuserdata 函数分配一块内存,并把对应的 userdatum 压入栈,并返回内存块地址。该内存的释...

  • lua C API(4) - 编写C函数其他技术

    数组处理数组处理可以用处理表格的方法来处理比如 lua_settable,lua_gettable,因为数组也是表格。但是出于性能考虑和简便性,lua api 提供了额外几个专门处理数组的函数。voi...

  • lua C API(3) - Lua 调用 C函数

    从Lua中调用C函数,必须遵循一些协议来传递参数和获得返回结果。另外,从Lua调用C函数我们必须注册函数,也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。每一个函数都有他自己的私有...

  • lua C API(2) - C 调用 Lua

    Lua 可以作为程序库用来扩展应用的功能。同时,Lua 程序中可以注册有其他语言实现的函数,这些函数可能由C语言(或其他语言)实现,可以增加一些不容易由 Lua 实现的功能。这使得 Lua 是可扩展的...

  • lua C API(1) - 函数汇总

    在调用C API时有几个重要的头文件:lua.h:基础函数库,lua_ 前缀。lauxlib.h辅助库,luaL_ 前缀,利用 lua.h 实现的更高层的抽象。lualib.h为了保持Lua的苗条,所...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。