在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
按:最近看到了依云的文章,一方面,为Lua被人误解而感到十分难过,另一方面,也为我的好友,
依云没有能够体会到Lua的绝妙和优雅之处而感到很遗憾,因此我写了这篇文章,逐条款地说明了
依云理解中出现的一些问题。希望能够帮助到大家!
1. 协程只能在Lua代码中使用 是的,协程在当你需要挂起一个C函数的时候无法使用。但是,在提出这个缺陷的时
候,是不是应该想一想:为什么Lua会有这个缺陷?
原因很简单:这一点完全避不开,这是C的问题,C无法保存函数执行的某个现场用于
返回这个现场继续执行,因此完全没有办法在Lua的协程被唤醒的时候,回到这个现场。
那么怎么办呢?Lua5.2做出了很优秀的设计。既然无法回到C的现场,那么我们不回
去了,而是采取“事件通知”的方式告诉你,“hey哥们,你前面的逻辑被切了,想办法
补救吧”,这就是所谓的CPS——继续风格的编程。继续在这里是一个Scheme/Lisp术
语,意思是“当前的操作执行完了以后,下面该做什么?”这种风格是Lua能支持任意
Yield 的必要条件。在C的限制下,只有这一种方法能突破这个限制。
至于你说的“比异步回调更复杂”,我想你弄混了两点:1.这只是C API层面的修改
,完全不影响到Lua代码层面,你的Lua代码完全不必做出任何修改,而且,你对
coroutine的用法完全错了!等会儿我会教你coroutine到底怎么用。2.上面提到了,
这是唯一一种能支持coroutine的方式,既然是唯一一种,就无所谓复杂与否了。3.
我下面会演示给你,为什么说coroutine完全解放了程序员,使用coroutine的代码会带来
革命性的简化。
我们分两步来说明这个问题:第一步,我们先来看你的例子:你想做的事情是,在执
行 c.callback的时候,能够yield出来,继续其他的流程。这里必须要说明,你的API设
计本身就是callback式的,因此这种API本身就犯不着coroutine,Lua本身能完全地处理
。这里我会给出一个支持coroutine的C模块设计,让这个模块能支持coroutine,第二步
,我会告诉你coroutine实际上是用在什么方面的,是如何取代事件回调机制的。在完成
这个说明后,我们来说明coroutine到底有什么好处,为什么说coroutine比事件回调机制
有着 革命性的优秀之处。
你的例子是这样的:
先说一下,将模块放到全局变量里通常不是一个好主意。所以第一行如果写成
就更好了。
其他的地方倒是没什么需要修改的了。
再看看你的C模块代码:
首先,因为这是Lua的C模块,所以你得声明这的确是一个C模块,应该在
#include <lua.h>
之前加入这一行:
#define LUA_LIB
编译的时候就可以用下面的命令行了:
gcc -mdll -DLUA_BUILD_AS_DLL c.c -oc.dll
然后,Lua5.2已经没有luaL_register函数了,因为Lua不鼓励将模块设置到全局域,
而luaL_register会做这件事。所以将这行改为:
luaL_newlib(L, c);
最后一点不是问题,只是一个小建议:Lua只是会用luaL_Reg里的内容,但是却不会
保留里面的任何内容,所以你可以直接将其放在luaopen_c里面,并去掉static,这样可
以节省一点内存。
我们来看看一个支持coroutine的C模块应该怎么写:
现在,你的例子可以完美运行了:
我们看到,让C模块支持yield是非常简单的:首先,你需要将lua_call/lua_pcall改
成对应的k版本,将函数其后的所有内容剪切到对应的cont函数里去,然后将原先的内容
改为return func_cont(L);即可。
为什么要这么设计API?上面说了,这是为了解决C自身的问题,如是而已。
现在我们来讨论第二个问题:Lua的coroutine用在什么地方呢?
假设我们要书写游戏的登陆逻辑,我们需要干这样的事情:
1. 登陆游戏
2. 获取玩家角色数据
3. 让玩家移动到上次退出前的坐标
如果是事件回调引擎,你会怎么设计API呢?可能是这样的:
看到了么?因为登陆需要等待网络请求,而等待的时候你不能把事件循环给阻塞了,
所以你不得不用回调机制,但是,一旦你一次要做几件事情,回调立即就会让你的代码狼
狈不堪。这还只是简单的顺序代码。如果是判断或者是循环呢?我告诉你,上面的代码是
一个真实的例子,是我以前设计的手机网游里面关于登陆部分的实际例子,而另一个例子
是在客户端批量购买N个道具!可以想象这会是一个很复杂的递归代码了,而实际上你仅
仅是想做for在做的事情而已!
那么怎么办呢?coroutine提供了解决这个问题的一个极端优雅的办法。我们想想最
优雅的设计会是什么样子的:
是不是简单多了?慢着!看起来login等函数是阻塞的,这样的阻塞难道不会阻塞事
件循环,导致界面僵死么?好!现在coroutine上场了!看看我们是如何实现login的!
看到了吗?login先用正常的方式调用了基于回调的callback_login,然后设置当前
在等待的coroutine为自身,最后yield掉自己。在回调执行的时候,回调会resume那个上
次被yield掉的coroutine,这样就完美的支持了阻塞的语法并且还能够满足事件循环的约
束!能够重新整理程序的执行流程,这就是coroutine的强大之处。最奇妙的是,在
这个设计之中,回调中唯一会做的事情只有resume,而不是yield,这意味着**即使不修
改一行代码,现有的模型也可以完美支持这个模式**!
可以看出将回调模式的函数改造成协程模式的函数是很简单的,我们甚至可以写一个
高阶函数来做这件事:
这样,上面的login函数就很简单了:
server.login = coroutinize(server.login)
看到Lua在表达复杂逻辑时的巨大优势了吗?coroutine机制同样也是可以支持函数重
入的:如果一个函数被调用多次,那么对应被调用的回调调用时,对应的那个coroutine
会被resume。至于如何实现,就交给读者作为练习了。提示:Programming in Lua这本书
已经说明了该如何去做。
我们总结一下:
1. coroutine无法穿越C边界是C语言的固有缺陷,Lua无法在保持其代码是Clean
C的前提下完成这个impossible的任务。
2. 那么,要支持这个特性,就只有要求C模块的编写者能采用CPS的方式编程了
。当然Lua的代码可以完全不做任何修改。
3. 而,coroutine很少需要在C函数内部yield(可能有实际场景会需要,但事实
是在我所书写的上万行的Lua富coroutine的代码中,完全没有用到过这种策
略)。
4. 如果你能深入了解coroutine,你会发现即使coroutine无法在C内部yield,
coroutine依然可以展现其绝大多数的威力。
5. Lua本身的设计可以让Lua在表现极端复杂的逻辑关系时游刃有余。
2. 幽灵一般的 nil 我不否认,在我刚刚学习Lua的时候,我的确被nil坑过很多遍。我们先抛弃掉luaJIT
关于NULL设计的问题(这个设计本身也是一种无奈,而且LuaJIT毕竟并不能完全继承Lua
作者对Lua的理念),先来看看nil究竟是什么——从nil中,我学习到了,在遇到坑爹特
性之前,先不要急着抱怨,想想为什么作者会设计这么坑爹的特性。要么作者是比你低能
的傻逼,要么这么设计就的确是有充分的考虑和不得已的苦衷的。这点你想到过吗?
nil是一个表示“没有”的值。是的,就是真的“没有”,因此nil本身就是一个幽灵
——它除了表示“这里没有东西”以外,没有其他的任何含义!它不是None(None是一个
表示“空”的对象),它也不是NULL(NULL表示没指向任何地方的指针——总所周知指针
本身必定是有值的,哪怕那个值是NULL)。Lua的作者十分聪明的将“没有”这个概念也
引入了语言,并且还保持了语言的一致性:请问,将“没有”存入一个表里面,它如果不
消失,还能发生什么事呢?
那么如何表示“空”或者“没有指向任何地方的引用”呢?两个办法,你可以存入
false,或者可以用下面这个巧妙的方法:
看!Lua可以灵活的控制一个变量的实际作用域!让一个变量真正的凭空消失掉!这
一点即使是C或者C++都是做不到的(变量只有在作用域外才会消失),这也是Lua强大的
灵活性的一个佐证。
千万不要弄错了,nil代表“没有”,它不代表“空”,也不代表“没有被初始化的
值”,它只是没有而已。因此它就应该是幽灵般的。这是Lua的语言设计一致性带来的优
势,而不是坑爹的特性。如果说学不会的特性就是坑爹的特性,那么是不是C语言的指针
也是坑爹的特性呢?
其次,各种库定义的null值,本质上是代表微妙但不同的东西。仔细地体会其中的不
同,能让你更得心应手的使用那些库。如果你在这些库的互操作上感到困扰,请给库的作
者写邮件抱怨:Lua有一个很热情友好的社区!
3. 没有continue 是的,Lua一直不肯加入continue。为什么呢?因为repeat until。而为什么强调“
不添加不必要的特性”的Lua作者会舍弃掉“那么常见”的continue,却保留不那么常见
的repeat呢?是Lua的作者傻么?
不是。这是经过仔细设计的。我先告诉你答案,再仔细地分析:事实上,在加入
repeat以后,continue是逻辑上不可能实现的,而repeat语句实现了一个用其他的特性完
全无法取代的特性。
注意看repeat的结构:
repeat <block> until <exp>
全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论