免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 12195 | 回复: 19
打印 上一主题 下一主题

[其他] 【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 |只看该作者

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
3 [报告]
发表于 2013-01-15 15:18 |只看该作者
第一个是对的。lua_resume就是把yield当作return的lua_pcall(注意错误本身会导致coroutine被咔嚓)

第二个不对,看实现就可以了,头文件里面lua_yield实际上是在调用lua_yieldk(L, nrets, NULL, 0);这样。文档说的很清楚,no continuation,不是把当前函数当continuation。就是这样。

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
4 [报告]
发表于 2013-01-15 15:23 |只看该作者
文档其实不是不清,需要一些背景知识,比如对resume的本质的了解和对continuation这个概念本身的了解,以及对CPS的了解……

参看我这篇文章:http://sw.is-programmer.com/2013 ... -caveats.36905.html

论坛徽章:
26
处女座
日期:2016-04-18 14:00:4515-16赛季CBA联赛之深圳
日期:2020-06-02 10:10:5015-16赛季CBA联赛之广夏
日期:2019-07-23 16:59:452016科比退役纪念章
日期:2019-06-26 16:59:1315-16赛季CBA联赛之天津
日期:2019-05-28 14:25:1915-16赛季CBA联赛之青岛
日期:2019-05-16 10:14:082016科比退役纪念章
日期:2019-01-11 14:44:062016科比退役纪念章
日期:2018-07-18 16:17:4015-16赛季CBA联赛之上海
日期:2017-08-22 18:18:5515-16赛季CBA联赛之江苏
日期:2017-08-04 17:00:4715-16赛季CBA联赛之佛山
日期:2017-02-20 18:21:1315-16赛季CBA联赛之天津
日期:2016-12-12 10:44:23
5 [报告]
发表于 2013-01-15 18:17 |只看该作者
coroutine 机制感觉好不靠谱,有人敢在项目里面用吗

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
6 [报告]
发表于 2013-01-15 18:21 |只看该作者
回复 5# evaspring


    一直在用啊~~很好用啊,很可靠呀~~

论坛徽章:
3
巳蛇
日期:2013-10-03 10:41:48申猴
日期:2014-07-29 16:12:04天蝎座
日期:2014-08-21 09:24:52
7 [报告]
发表于 2013-01-15 18:28 |只看该作者
博客风格又换了..

论坛徽章:
0
8 [报告]
发表于 2013-01-15 18:40 |只看该作者
starwing83 发表于 2013-01-15 18:21
回复 5# evaspring


我前段时间在oschina上看到有人写了篇博客说:
coroutine比线程好,切换代价小,并且不需要锁等一系列优点.
作者展示了用C++实现coroutine机制,并用来作为一个服务器的框架,
作者说他的这种框架要比epoll reactor线程池的模型要高明.

我没有仔细了解coroutine,但说它要好过线程,我不苟同。
因为如果真有作者说的那么好,OS设计人员或者用户层的库的作者们应该会跟进.

论坛徽章:
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-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
10 [报告]
发表于 2013-01-15 23:42 |只看该作者
@starwing83

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

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

第一个pcallk的例子, 第一次resume进 ...
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP