Archive for 三月 2013

Lua5.1.4代码分析(十九)-Lua协程的实现

协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制;另外,协程也更加轻量级。这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU;而当条件满足时,可以继续执行这个协程。目前在网络服务器领域,使用Lua协程最好的范例就是ngx_lua了,我自己的项目qnode也是借助Lua协程的概念:每一个qnode中的微进程底层对应一个Lua协程,这样底层的异步操作可以在使用者使用同步的方式写出来。Coool。

来看看Lua协程内部是如何实现的。

本质上,每个Lua协程其实也是对应一个LuaState指针,所以其实它内部也是一个完整的Lua虚拟机—有完整的Lua堆栈结构,函数调用栈等等等等,绝大部分之前对Lua虚拟机的分析都可以直接套用到Lua协程中。于是,由Lua虚拟机管理着这些隶属于它的协程,当需要暂停当前运行协程的时候,就保存它的运行环境,切换到别的协程继续执行。很简单的实现。

来看看相关的API。

1)

内存池及其他

最近在看nginx的代码,看了一下nginx内存池和字符串相关的代码。很简单的代码,就不分析啥了。

对类tcmalloc之类的内存分配库有一定了解的人,都知道其实这些库内部也都会做缓存,不会每次分配就找系统要每次释放就返回系统,因此我一直质疑有没有必要在应用层自己再做一次类似内存池这样的缓存。

回到nginx中,它的内存池算法及其简单,使用时是绑定在比如连接相关的结构体上,一个连接到来分配一个内存池,之后这个连接相关的内存分配操作都在这个内存池中进行,而在连接关闭之后与之相关的内存池就随之关闭。换言之,nginx试图通过内存池这个概念,将内存管理的粒度做的更小,比系统级的更小,这样也更易于管理。而nginx的字符串结构体,仅有一个指针指向字符串和一个保存字符串长度的整型变量,这个字符串在分配之后不可变,那么要分配可变字符串呢,首先使用内存池来分配内存,然后使用类sprintf的函数预先格式化好字符串,随后的事情就交由这个结构体管理。

说了这么多,我想说的是,这几个结构体的设计,将它们的功能尽可能的最小化,正交化:内存池只管内存,与特定的数据结构绑定,生死与它一起;字符串只关心自己的内容,一旦分配不可改变,也不关心内存分配问题。

而对比起来,我自己做的qnode项目,字符串相关的操作就做了以上的所有事情:内存分配,字符串内容管理,支持可变的API,等等等等。太多太多了。

以上,可以解释为什么需要在应用层设计一个内存池,和如何设计数据结构及相关API的好范例。

——————