免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: linux_c_py_php
打印 上一主题 下一主题

[其他] 【lua5.2相关】 今天做了一番试验, 发现coroutine有此坑, 真神秘。 [复制链接]

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-01-15 15:11 |显示全部楼层 |倒序浏览
环境是lua5.2, 主要是把5.1->5.2的api change list过了一遍, 主要想验证coroutine的工作过程, 然后发现了两个坑, 大侠看看我理解的对不对, 多谢.

1, 写C模块命名为"mod", 写C宿主调用"mod", 其中"mod"模块只包含一个函数, 用于测试coroutine.
  1. [root@vps616 study]# cat src/mod.c
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"

  5. static int run_twice(lua_State *L) {
  6.     int ctx;

  7.     if (lua_getctx(L, &ctx) == LUA_OK) {
  8.         lua_pushstring(L, "hello");
  9.         return lua_yieldk(L, 1, 2, run_twice);
  10.     }
  11.     lua_pushstring(L, " world");
  12.     return 1;
  13. }

  14. static const luaL_Reg reg_api[] = {
  15.     {"run_twice", run_twice},
  16.     {NULL, NULL}
  17. };

  18. int luaopen_mod(lua_State *L) {
  19.     lua_newtable(L);
  20.     luaL_setfuncs(L, reg_api, 0);
  21.     return 1;
  22. }
复制代码
  1. [root@vps616 study]# cat src/main.c
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"

  5. int main(int argc, char* const argv[]) {
  6.     lua_State *L = luaL_newstate();
  7.     lua_State *LL = lua_newthread(L);
  8.    
  9.     lua_pushcfunction(L, luaopen_base);
  10.     if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
  11.         return 1;
  12.     }
  13.     lua_pushcfunction(L, luaopen_package);
  14.     if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
  15.         return 2;
  16.     }
  17.     lua_getglobal(L, "require");
  18.     lua_pushstring(L, "mod");
  19.     if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
  20.         return 3;
  21.     }
  22.     lua_getfield(L, -1, "run_twice");
  23.     lua_pushstring(LL, "1234");
  24.     printf("top=%d\n", lua_gettop(LL));
  25.     lua_xmove(L, LL, 1);
  26.     printf("top=%d\n", lua_gettop(LL));
  27.     if (lua_resume(LL, NULL, 0) == LUA_YIELD) {
  28.         printf("yield=%s\n", lua_tostring(LL, -1));
  29.     }
  30.     printf("top=%d\n", lua_gettop(LL));
  31.     if (lua_resume(LL, NULL, 0) == LUA_OK) {
  32.         printf("return=%s\n", lua_tostring(LL, -1));
  33.     }
  34.     printf("top=%d %s\n", lua_gettop(LL), lua_tostring(LL, 1));
  35.     return 0;
  36. }
复制代码
这个例子主要目的是观察run_twice在协程中执行的栈变化, 因为看lua.org/manual实在是描述的不太清晰.
  1. [root@vps616 study]# bin/main
  2. top=1
  3. top=2
  4. yield=hello
  5. top=1
  6. return= world
  7. top=2 1234
复制代码
执行结果如下, 可见, yield=hello后top=1, 是yield的返回值, 而return= world之后top=2, 栈底是1234, 栈顶是 world.
我得到结论就是: 在协程未return消亡前, 协程的栈只会容纳resume与yield彼此交换的参数, 直到return才会重新将原本thread栈上的值暴露出来.
这一点和直接调用一个普通函数一样, 在函数内部(相当于thread未return)来看栈只容纳了参数, 其他参数与函数之前的内容暂时屏蔽掉了.


上面这个感悟是否和谐, 求指正.



再就是之前在5.1里被lua_yield坑过, 那个版本yield只能抛回给lua函数而不能抛回给c函数, 5.2有了k系列就不一样了, 但我看5.2手册里lua_yield的说明太朦胧:
int lua_yield (lua_State *L, int nresults);

This function is equivalent to lua_yieldk, but it has no continuation (see §4.7). Therefore, when the thread resumes, it returns to the function that called the function calling lua_yield.


我以为这句话的描述是lua_yield相当于lua_yieldk的function传入当前的function, 但实际上当再次resume的时候并没有重新调用发起lua_yield的那个函数, 而是当再次resume直接返回true, 栈也是空的, 我就是很郁闷了.

根据这个结论, 我认为lua_yield是在lua_pcallk/lua_callk的callee函数中调用的, 这样再次resume执行的是lua_pcallk/lua_callk指定的function。

这个结论是否和谐?

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
2 [报告]
发表于 2013-01-15 15:14 |显示全部楼层

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
3 [报告]
发表于 2013-01-15 23:41 |显示全部楼层
本帖最后由 linux_c_py_php 于 2013-01-16 00:07 编辑

拜读了, 前两个例子非常深刻, 一开始也不好理解, 好在最后都理解了.

第一个pcallk的例子, 第一次resume进入wrap, 调用c_callback, 从而通过pcallk直接yield到wrap(这里pcallk里如果发生了yield, 直接就goto走了, pcallk根本不会返回, 再次resume也是进入c_cont并返回), 第二次resume进入c_cont并从c_callback函数返回到wrap内的调用点, 最后由wrap自己return nil.

我原本以为再次resume后return是返回到coroutine, 没想到这里是返回到c_callback的调用点...

C里调coroutine真是绕脑子, 应该是我没用过pcallk的原因, 都是直接newthread然后就lua_resume了, 所以产生这种错误认知, 这个错误认知应该是因为直接lua_resume->yield和resume->pcallk->yield中间的pcallk没有关注过.

这个理解对吗? 自我感觉pcallk这里不太对劲, 求讲解. 就是说, pcallk调用的函数里yield是返回到resume, 而return是返回到哪里呢? 我试了一下, 不是返回到pcallk, 而是返回到c_callback, 这是梦吗...

第二个例子一开始没看懂, 硬是读了几遍描述, 才搞清楚coroutine只是作用在Login这个逻辑上, 即server::login是登陆接口, 但它是不阻塞的, 在设置了login的callback后, 立即yield, 相当于这个登陆coroutine线程的CPU让出了, 等外部继续epoll等事件循环并触发Login的callback后, 会调用coroutine.resume将cpu让给server::login, 完成登陆.

实际上这个过程没有并发行为, 执行是串行的, 但coroutine帮忙让代码看起来像是阻塞的, 并且利用thread的栈保持了登陆的状态, 也可以说是一个断点, 等待事件触发后推动断点.


starwing83 发表于 2013-01-15 15:23
文档其实不是不清,需要一些背景知识,比如对resume的本质的了解和对continuation这个概念本身的了解,以及 ...

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
4 [报告]
发表于 2013-01-15 23:42 |显示全部楼层
@starwing83

哥, 再指导指导吧, 我快找到感觉了.

linux_c_py_php 发表于 2013-01-15 23:41
拜读了, 前两个例子非常深刻, 一开始也不好理解, 好在最后都理解了.

第一个pcallk的例子, 第一次resume进 ...

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
5 [报告]
发表于 2013-01-16 00:31 |显示全部楼层
本帖最后由 linux_c_py_php 于 2013-01-16 00:32 编辑

但是我在lua里实现相同的东西, 再次resume的返回点却是callback函数内部, 而不是像c_callback那样直接返回到wrap调用c_callback的位置.

这个原理怎么分析...
  1. [root@vps616 study]# ./main.lua   
  2. coroutine yielding
  3. 5
  4. coroutine resumed
  5. the end
  6. [root@vps616 study]# cat main.lua
  7. #!/usr/bin/env lua

  8. --c = require('c')                 
  9.   
  10. function callback(f)
  11.     n = f()
  12.     print(n)
  13. end

  14. co = coroutine.create(function()
  15.   print('coroutine yielding')   
  16.   callback(function()         
  17.     coroutine.yield()
  18.     return 5
  19.   end)
  20.   print('coroutine resumed')   
  21. end)                             
  22.                                  
  23. coroutine.resume(co)
  24. coroutine.resume(co)
  25.                                  
  26. print('the end')  
复制代码
linux_c_py_php 发表于 2013-01-15 23:42
@starwing83

哥, 再指导指导吧, 我快找到感觉了.

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
6 [报告]
发表于 2013-01-16 00:39 |显示全部楼层
本帖最后由 linux_c_py_php 于 2013-01-16 00:58 编辑

与之对应的C实现只能是这样, 根本不会从pcallk返回, 而且是直接从c_cont中return到c.callback被调用位置, 为何有差别? 无法实现成一致的么? 什么道理?
  1. [root@vps616 study]# ./main.lua   
  2. coroutine yielding
  3. 5
  4. coroutine resumed
  5. the end
  6. [root@vps616 study]# cat main.lua
  7. #!/usr/bin/env lua

  8. c = require('c')                 
  9.   
  10. co = coroutine.create(function()
  11.   print('coroutine yielding')   
  12.   n = c.callback(function()         
  13.     coroutine.yield()
  14.   end)
  15.   print(n)
  16.   print('coroutine resumed')   
  17. end)                             
  18.                                  
  19. coroutine.resume(co)
  20. coroutine.resume(co)
  21.                                  
  22. print('the end')  
  23. [root@vps616 study]# cat src/mod.c
  24. #include<stdio.h>                                                     
  25. #include<stdlib.h>                                                   
  26. #include<lua.h>                                                      
  27. #include<lualib.h>                                                   
  28. #include<lauxlib.h>                                                   
  29.                                                                        
  30. static int c_cont(lua_State *L) {                                    
  31.     lua_pushinteger(L, 5);
  32.     return 1;  //这里返回到了c_callback被调用的位置, 也就是lua里的调用点                                                      
  33. }                                                                     
  34.                                                                        
  35. static int c_callback(lua_State *L){                                 
  36.   int ret = lua_pcallk(L, 0, 0, 0, 0, c_cont);                        
  37.   if(ret) {                                                           
  38.     fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));              
  39.     lua_pop(L, 1);                                                   
  40.     exit(1);                                                         
  41.   }                                                                  
  42.   return 0;
  43. }                                                                     
  44.                                                                        
  45. static const luaL_Reg c[] = {                                         
  46.   {"callback", c_callback},                                          
  47.   {NULL, NULL}                                                        
  48. };                                                                    
  49.                                                                        
  50. int luaopen_c (lua_State *L) {                             
  51.   /* 使用新的 luaL_newlib 函数 */                                    
  52.   luaL_newlib(L, c);                                                  
  53.   return 1;                                                           
  54. }
复制代码

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
7 [报告]
发表于 2013-01-16 11:49 |显示全部楼层
非常感谢starwing83, 很受用, 有问题再找你哈.

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
8 [报告]
发表于 2013-01-21 18:58 |显示全部楼层
@starwing83

追加个问题, 对于blog里的例子, 如果coroutine.create的这个function我不用lua写, 而是在c宿主程序里传入一个c的function(先调用printf,再调用lua里的c.function,再调用c的printf)并在c里resume, 那么当再次resume时, 执行c_cont后return到的应该直接是resume点, 而不会是c.callback的调用点吧, 第一次yield应该已经破坏掉了除了main thread以外的所有C堆栈了吧.

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
9 [报告]
发表于 2013-01-21 19:01 |显示全部楼层
晚上回家会自己试验一下, 大哥有空也给指教指教.

我的感受是lua是解释器执行, 所以它的堆栈是可以维持的, 并在再次resume时, 避开c.callback的堆栈破坏不说, 应当可以正常从c.callback(c_cont)返回.
但如果这个wrapper是c的,那么resume的这个wrapper的C堆栈也会被破坏,没法继续执行。

linux_c_py_php 发表于 2013-01-21 18:58
@starwing83

追加个问题, 对于blog里的例子, 如果coroutine.create的这个function我不用lua写, 而是在c宿 ...

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
10 [报告]
发表于 2013-01-21 20:31 |显示全部楼层

very good, thanks very very much!

starwing83 发表于 2013-01-21 19:16
回复 17# linux_c_py_php
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP