免费注册 查看新帖 |

Chinaunix

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

[其他] 【(LUA相关)一个支持yield的server能避免掉轮询检查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-02-23 23:14 |只看该作者 |倒序浏览
本帖最后由 linux_c_py_php 于 2013-02-23 23:16 编辑

比如, 我封装了一个C的接口给LUA, 用来完成非阻塞connect(对lua使用者来看是阻塞逻辑), 在C实现上借助了server的事件循环完成unblocked connect.

在我的认识中, 无论事件循环中非阻塞connect成功还是失败, 作为server来说只有判断coroutine的status是dead才能认为lua脚本执行结束(当然server可以通过不同的resume值来通知lua脚本连接成功或失败, 然后lua可以继续执行直到死亡), 或者是server认为coroutine运行超时直接回收该coroutine及其注册的非阻塞连接事件.

而check这个coroutine is dead的时机, 似乎也只能是server来轮询coroutine是否已经dead了, 不知道这个观点是否正确.

如果观点正确, 那么对于客户端程序来说, 似乎单线程的IOCP来做网络收发并轮询各个lua status会是在WIN32上对应的做法了吗?

论坛徽章:
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-02-23 23:17 |只看该作者
@starwing83 速来指点, 实在也没投入决心去看看nginx-lua之类的实现方式.

论坛徽章:
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-02-24 14:28 |只看该作者
说实话没看懂,这样,我说一下coroutine和回调的联系吧。

假设我有一个回调接口在Lua上,这个接口的实现是很容易的,假设回调是这样的:

local server = require 'server' .new()

servern_connect(function(...)
   ...
end)

server:loop()

然后在连接的时候,调用on_connect注册的函数,当然还可以有很多的函数。在loop的时候等待客户端的连接,在accept的时候执行这个回调,这很明白了吧?

所谓的“coroutine”的优势,其实只有在多个回调的情况下才会出现。好,现在我们假设除了server以外,我们还有一个模块叫client,这个client模块负责产生某些连接:

local client = require 'client' .new()

client:connect(function(...)
  ....
en)
client:connect(function(...)
  ....
end)

client:loop()

我们创建了两个不同的连接,然后loop中对连接的结果进行回调,这个很简单吧?具体的实现大概是这样的:每次connect都会产生新的fd,然后会通过select/epoll完成loop的内容。注意!无论是select/epoll,都能在返回的时候实时反馈所有fd的状态。因此,在这两个函数返回的时候,你可以选择:

1. 调用有事件的函数的回调(比如cb("read", data...)或者cb("write", status...))
2. 当fd出错被关闭,将某些资源(主要是函数)标记为可以回收,当然回收前通知一下也可以

关键是!epoll不是通过轮询去获知所有的fd的状态的,而是会直接返回状态有改变的fd。对Lua来说,这也是一样的。

现在回到coroutine话题。这和回调是一一对应的。

1. 回调的注册,类似于心的coroutine的创建
2. 对回调的调用,类似于对coroutine的lua_resume
3. 对回调资源的清理,类似于对coroutine本身资源的清理

我们举个实际的例子。

假设我同时下载两个网站A和B的资源。现在用coroutine做这件事情。代码如下:

local res, err = client:download("A"
if err then ... end
print(res)

那么,操作是这样的:
1. 将上面的chunk做成一个function:lua_load(....)
2. 创建一个新的coroutine:lua_newthread(L);
3. 将chunk放到coroutine上面去:lua_xmove(或者直接在coroutine上lua_load)
4. 将coroutine和fd关联:registry.callbacks[fd] = co
5. loop的时候epoll/select
6. 当得知某个fd的状态改变时,查询到对应的co:co = registry.callbacks[fd]
7. 调用这个coroutine:lua_resume(co, ...)
8. 在清理fd的时候,清理那个注册表:registry.callbacks[fd] = nil

就是这样的。完全不需要轮询。

论坛徽章:
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-02-24 14:45 |只看该作者
本帖最后由 linux_c_py_php 于 2013-02-24 15:55 编辑

额 你说的我懂 我的意思是脚本哪时候结束 我理解宿主只能轮询去查看coroutine的状态 或者在宿主里为lua代码做c wrapper并在lua代码return后在c wrapper里通过管道来通知事件event loop。 不知道这样表述清晰否

论坛徽章:
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-02-24 14:52 |只看该作者
你说的是做connect接口resume的时机 我是问脚本结束怎么来发现 这个意思

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
6 [报告]
发表于 2013-02-24 15:53 来自手机 |只看该作者
在lua脚本最后加一个异步通知,把这个session消耗就好了,在外面做有点麻烦

论坛徽章:
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-02-24 16:54 |只看该作者
本帖最后由 linux_c_py_php 于 2013-02-24 17:05 编辑

举个简单例子, 服务端假设提供各个插件(包括lua插件)提供完成的回调通知接口, 那么为宿主写一个lua扩展可以大致像这样来通知event loop该lua脚本执行结束了(这里直接在coroutine内的第0级调用上就做了yield, 演示用途.):
  1. [root@vps616 study]# bin/main
  2. write 1 byte to pipe to notify event loop that lua finishes
  3. [root@vps616 study]# cat logic.lua
  4. co = coroutine.running()
  5. coroutine.yield(co)
  6. [root@vps616 study]# cat src/main.c
  7. #include <lua.h>
  8. #include <lualib.h>
  9. #include <lauxlib.h>

  10. int luaCoroutineFinish(lua_State *LL) {
  11.     /*lua_getctx can check if error occurs in luascript, so we can return differet result to lua_resume*/
  12.     int ctx = 0;
  13.     int err = lua_getctx(LL, &ctx);
  14.     printf("err=%d ctx=%d write 1 byte to pipe to notify event loop that lua finishes\n", err, ctx);
  15.     if (err != LUA_YIELD) {
  16.         printf("%s\n", lua_tostring(LL, -1));
  17.     }
  18.     return 0; /*coroutine dead here*/
  19. }

  20. int luaError(lua_State *LL) {
  21.     printf("lua script error, we may pushback a errmsg for luaCoroutineFinish\n");
  22.     lua_pushstring(LL, "cao");
  23.     return 1;
  24. }

  25. int luaCoroutine(lua_State *LL) {
  26.     lua_pushcfunction(LL, luaError);
  27.     luaL_loadfile(LL, "./logic.lua");
  28.     if (lua_pcallk(LL, 0, 0, -2, 0, luaCoroutineFinish) != LUA_OK) {
  29.         lua_pushstring(LL, "fuck");
  30.         lua_error(LL);     
  31.     }
  32.     printf("Also write 1 byte to pipe to notify event loop that lua finishes\n");
  33.     return 0; /*coroutine dead here*/
  34. }

  35. int main(int argc, char *const argv[]) {
  36.     lua_State *L = luaL_newstate();
  37.     luaL_openlibs(L);

  38.     lua_State *LL = lua_newthread(L);
  39.     lua_pushcfunction(LL, luaCoroutine);
  40.     if (lua_resume(LL, NULL, 0) != LUA_YIELD) {
  41.         printf("___1:%s\n", lua_tostring(LL, -1));
  42.         return -1;
  43.     }
  44.     if (lua_resume(LL, NULL, 0) != LUA_OK) {
  45.         printf("___2:%s\n", lua_tostring(LL, -1));
  46.         return -1;
  47.     }
  48.     return 0;
  49. }
复制代码

论坛徽章:
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-02-24 17:11 |只看该作者
本帖最后由 linux_c_py_php 于 2013-02-24 17:11 编辑

至今让我受益终生的就是@starwing83给我讲的lua栈和c栈的生命期那些基本原理, 用lua一下就没压力了, 必须跪谢.

论坛徽章:
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
9 [报告]
发表于 2013-02-24 17:29 |只看该作者
回复 5# linux_c_py_php


    你的问题好奇怪,这样我先解答一下“coroutine何时结束”这样的问题,然后我来看看代码看看问题究竟出在哪儿= =

coroutine这种东西和thread这种东西不一样的地方是,这货的执行脉络不仅是可预知的,而且还是能被完全控制的。

什么意思呢?当你调用一个函数的时候,你会纠结“这个函数啥时候结束”么?不会吧?

当你为了某个目的需要连续调用一系列函数,你会纠结“这个函数何时结束”么?可能会吧,那怎么办呢?这时你需要通知coroutine,“我准备结束掉你”,然后等待coroutine自己结束自己,而且这个过程绝不会造成“coroutine就是不退出”这种尴尬的事情。

我来仔细的说明。

对于单次coroutine的“调度”,本质上就是将coroutine从“当前悬着的执行点”一直执行到“return或者yield”这么一个过程,当作一个函数去调用的过程。换句话说,lua_resume就是一个简单的函数而已。如果你有一个事件,这个事件完成了,我因为事件的完成需要回调,那么lua_resume好了,lua_resume以后coroutine必然有个状态——要么是继续别yield到另外一个状态了——这个时候你的yield函数自己应该已经记录了这个被yield的coroutine(如果没有,那么现在就是记录的时间了),要么你的coroutine应该已经执行完成了——这可以从lua_resume的返回值得到。如果执行完了,那么coroutine可以就可以扔掉(或者很简单地,不去记录),它自然会被垃圾回收掉。

如果是多次调用呢?这里就涉及到这个coroutine设计和整体的一个设计框架问题了。假设我有很多很多的阻塞函数,调用其他的任何一个都会导致coroutine被阻塞,coroutine的逻辑就在这不断地等待中继续。这种情况下,直接去调用coroutine.yield应该是违法的(你yield干嘛呢?),而是我去调用那些阻塞函数,阻塞函数记录你的事件存根(回调上下文)和coroutine的关系,然后yield,当事件产生时,根据存根找到coroutine(注意,这个关系本身会保持coroutine的生命),然后lua_resume,直到下一个阻塞函数记录了你的事件存根,或者因为执行完毕被咔嚓。上一段是从实现的角度分析,这一段是从coroutine的角度来观察。

那么,记住,coroutine本身绝对不是异步的,它的每次执行都是已知的,根本不存在异步的问题,而且coroutine本身不能用在多线程的环境中的,(Lua内部线程不安全,而如果人工设置lua_lock和lua_unlock会导致Lua锁住所有的执行,依然是一次执行一个过程),所以完全不存在通知的问题,这就是我觉得你的问题很奇怪的原因。

说白了,要知道coroutine何时结束,你必须知道coroutine如何开始,这就是我为什么在上一楼很详细地告诉你coroutine如何开始的原因,当coroutine通过某个函数调用(一定是lua_resume)开始以后,这个函数的返回必定是coroutine告一段落的时候,这个函数如果返回LUA_OK,那么coroutine必然就已经寿终正寝,否则coroutine内部做了yield,剩下的就完全是设计问题,跟coroutine本身没关系了。

千万弄清楚lua_resume和k系列函数的区别:
- lua_resume在任何情况下,都不可打断,不会跳转地开启一个coroutine,并一定会返回一个结果
- lua_(p)callk调用一个函数,这个函数可能会导致yield,如果yield,那么resume的时候,调用k作为代替
- lua_yieldk直接yield一个coroutine,在下次resume的时候,调用k作为执行的延续。

记住,开启一个coroutine的执行的时候,是不需要k的,而当你在coroutine内部害怕你会被下一层咔嚓掉的时候,就需要k来上保险了。


论坛徽章:
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
10 [报告]
发表于 2013-02-24 17:40 |只看该作者
本帖最后由 starwing83 于 2013-02-24 17:41 编辑

下面是一个GUI中采用coroutine作为事件驱动的实际例子,展示了采用coroutine作为事件驱动,对代码的极度简化。

只要写过GUI的人,我想肯定都写过如何拖动屏幕上的一个矩形这样的代码。这种代码需要处理各种状态:鼠标左键按下的时候做标记,鼠标移动的时候检查标志,等等。看看一个一coroutine驱动的脚本如何做这样的事情吧:
  1. -- 一个单个函数就能拖动矩形的程序

  2. local win = require 'grapapp' .new()
  3. local rect = require 'geometry' .rect(10, 10, 20, 20)

  4. -- 拖动逻辑
  5. win:mouse_event(function(evt)
  6.     if evt:wait() == 'down' and rect:contains(evt) then
  7.         local dx = rect.x - evt.x
  8.         local dy = rect.y - evt.y
  9.         while evt:wait() == 'move' do
  10.             rect.x = rect.x + evt.x
  11.             rect.y = rect.y + evt.y
  12.             win:redraw()
  13.         end
  14.     end
  15. end)

  16. -- 绘制逻辑
  17. win:redraw_event(function(evt)
  18.     evt.g:draw_rect(rect)
  19. end)
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP