Chinaunix

标题: 如何在Lua5.1.4中实现这样的效果 [打印本页]

作者: converse    时间: 2013-01-25 15:12
标题: 如何在Lua5.1.4中实现这样的效果
本帖最后由 converse 于 2013-01-25 15:14 编辑

模拟一个场景,在C中创建出coroutine来执行Lua脚本,并且提供C API给Lua使用,当某些操作可能会阻塞时(如网络I/O),C函数中执行yield将协程切换出去,然后未来的某个时刻,如果条件满足则resume继续执行后面的脚本.我写了个demo程序是这样的:

co.c:
  1. #include <stdio.h>
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"

  5. static int panic(lua_State *state) {
  6.   printf("PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(state, -1));
  7.   return 0;
  8. }

  9. static int test(lua_State *state) {
  10.   printf("in test\n");
  11.   lua_yield(state, 0);
  12.   printf("after in test\n");
  13.   return 0;
  14. }

  15. int main(int argc, char *argv[]) {
  16.   char *name = NULL;
  17.   name = "co.lua";
  18.   lua_State*  L1 = NULL;
  19.   L1 = lua_open();
  20.   lua_atpanic(L1, panic);
  21.   luaL_openlibs( L1 );

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

  24.   luaL_dofile(L, name);
  25.   sleep(1);
  26.   lua_resume(L, 0);
  27.   printf("after resume test\n");

  28.   return 0;
  29. }
复制代码
co.lua
  1. print("before")
  2. test("123")
  3. print("after resume")
复制代码
问题在于,resume之后,在co.lua中的"after resume"没有打印出来
作者: converse    时间: 2013-01-25 15:13
@starwing83 来看看.
作者: linux_c_py_php    时间: 2013-01-25 15:59
static int test(lua_State *state) {
  printf("in test\n");
  lua_yield(state, 0);
  printf("after in test\n");
  return 0;
}

很明显错了, return lua_yield(state, 0)
作者: starwing83    时间: 2013-01-25 16:45
要达到这样的效果,在Lua5.2里面直接用yieldk即可。但是如果是5.1,那么需要满足下面的条件:

1. 你知道所有resume的地方。
2. 暗中yield的函数不会被递归调用。

达到这两个条件,我们就可以继续了。

实现方法其实非常简单。每一个函数由两个函数组成,一个是你的test,一个是你的test_cont,当准备yield之前,先做这样的一个操作:

conts[coroutine.running()] = test_cont

然后调用lua_yield(其实所有的操作未必需要C模块,Lua模块完全可以做得到)。

在resume之前,判断conts里面对应需要resume的coroutine有没有对应的cont,如果有,就将其push上coroutine,pcall之。然后用其结果resume。

这样就能达到楼主想要的效果了。

因为一个coroutine肯定只会有一个地方被断(不然就不止一个了= =),所以同一时间的continuation也应该只有一个才对。应该是不会出现问题的。
第二步resume前那个需要push函数然后pcall的部分,可能需要C模块来做,但是其实Lua模块也做得了——那样就没啥意义了= =你可以直接在yield后面写你想执行的代码,不必要绕这么大的湾……

本质上,Lua5.2只是帮你自动管理了这个表,如是而已……
作者: converse    时间: 2013-01-25 16:51
回复 4# starwing83

直接调用lua_yield,会报错:
attempt to yield across metamethod/C-call boundary


   
作者: linux_c_py_php    时间: 2013-01-25 17:00
楼主可以让test函数返回一个continue的函数以及其他返回值, 主线程再次resume就行了.

converse 发表于 2013-01-25 16:51
回复 4# starwing83

直接调用lua_yield,会报错:

作者: starwing83    时间: 2013-01-25 17:04
回复 5# converse


    所以要有对框架的完全控制能力才行啊,你必须保证yield和resume在同一个层次去做(都在C,或者都在Lua),不然就是错。

最简单的还是升级到5.2,5.1的coroutine毕竟有限制……
作者: starwing83    时间: 2013-01-25 17:06
回复 6# linux_c_py_php


    你觉得resume会直接调用栈顶函数?

不会的。

Lua有两个完全独立的栈。一个叫做数据栈,就是你平时看到的push/to所在的那个栈。还有一个隐藏着的,每coroutine的,不参与垃圾回收的叫做CallInfo栈。这个栈保存了每次函数调用的信息。所以,必须push到coroutine上面pcall,返回什么的是没有效果的……
作者: linux_c_py_php    时间: 2013-01-25 17:07
这样貌似也不一定行.

linux_c_py_php 发表于 2013-01-25 17:00
楼主可以让test函数返回一个continue的函数以及其他返回值, 主线程再次resume就行了.

作者: converse    时间: 2013-01-25 17:07
回复 7# starwing83


我自己做的项目,完全把控是没有问题的.

也能保证yield和resume都在同一层C/Lua来进行操作,但是就是上面的例子中,yield/resume都在C层做,但是还是出错的.你可否简单的改成正确的结果我看看?


   
作者: linux_c_py_php    时间: 2013-01-25 17:09
正是这个意思。

starwing83 发表于 2013-01-25 17:06
回复 6# linux_c_py_php

作者: converse    时间: 2013-01-28 14:08
  1. #include <stdio.h>
  2.     #include "lua.h"
  3.     #include "lualib.h"
  4.     #include "lauxlib.h"

  5.     static int panic(lua_State *state) {
  6.       printf("PANIC: unprotected error in call to Lua API (%s)\n",
  7.               lua_tostring(state, -1));
  8.       return 0;
  9.     }

  10.     static int test(lua_State *state) {
  11.       printf("in test\n");
  12.       printf("yielding\n");
  13.       return lua_yield(state, 0);
  14.     }

  15.     int main(int argc, char *argv[]) {
  16.       char *name = NULL;
  17.       name = "co.lua";
  18.       lua_State*  L1 = NULL;
  19.       L1 = lua_open();
  20.       lua_atpanic(L1, panic);
  21.       luaL_openlibs( L1 );

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

  24.       luaL_loadfile(L, name);
  25.       lua_resume(L, 0);
  26.       printf("sleeping\n");
  27.       sleep(1);
  28.       lua_resume(L, 0);
  29.       printf("after resume test\n");

  30.       return 0;
  31.     }
复制代码
搞定.




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2