lua C API(6) - 协程和state

我们看看如何在 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 源码,在这里暂时不研究。


上一篇: lua C API(5) - 用户自定义类型
下一篇: 无
作者邮箱: 203328517@qq.com