2.1 MainLoop
main loop是Glib中一个非常重要的部分,其主要用途类似于Windows的消息循环。所以它是一个循环,不停得从某个地方取得“消息”,然后派发到消息处理函数,交给他们处理。
从执行者角度看,派发到消息处理函数实际还是消息循环所在的线程调用对应的函数。
在Glib中,消息循环对应的就是这个mainLoop。消息队列没有对应物。但是消息的产地由GSource标示,例如一个文件描述符,一个socket都是某种source。Glib提供了GMainContext来管理这些source。而mainLoop只要管理Context即可管理很多source。
其实很简单,仔细比较下windows下的线程函数就可知道。Windows下的线程函数一般会循环等待一些事件,例如
while(TRUE)
{
WaitForMultiObjects(count,arry,...);
其中arry是一个事件数组。
}
仔细想想,mainLoop是上面那个while循环的对象封装,context是arry数组的对象封装,而arry里边的成员就是source的对象封装。
通过这种方式的对象封装,在不同线程里边直接使用mainLoop对象,或者切换context对象就可以做到非常方便简洁。
循环退出,必须有个事件处理中调用g_main_loop_quit才可以。
如果你能这么理解MainLoop的话,就非常容易从WIN-API转到GLib中去了。
实际上,MainLoop的整个系统比较精巧,涉及到定时源,空闲源,优先级等。
我看了下Glib的实行代码,内部对应WaitForMultiObjects的是poll函数。可想,如果有很多事件的话,由poll本身造成的效率损失将比较大。另外它是在一个线程中执行处理函数的,所以会影响后续事件的处理。
下面将详细讲述和比较。
1.迭代
其实就是强制调用一次WaitForMultiObjects以检查发生了什么事件。Glib对context分了好几种状态。
图 Context状态
状态只有context才有,想想和Wait函数的那个比较。待会再解释那几个状态以及对应函数的原型说明。
从这个图以及源代码看,一次iteration调用包括先prepare下,然后调用poll函数,然后调用check—>dispatch。具体干了什么,下面再说。其实就是讨论wait函数的内部实现
这些个状态的确定是需要调用对应源的函数来确定的。而源函数由GSourceFuncs来确定。
typedef struct {
gboolean (*prepare) (GSource *source,
gint *timeout_);
gboolean (*check) (GSource *source);
gboolean (*dispatch) (GSource *source,
GSourceFunc callback,
gpointer user_data);
void (*finalize) (GSource *source); /* Can be NULL */
/* For use by g_source_set_closure */
GSourceFunc closure_callback;
GSourceDummyMarshal closure_marshal; /* Really is of type GClosureMarshal */
} GSourceFuncs;
prepare函数:将调用每个source的这个函数以确定哪些已经准备好了,source在这些函数里边可以判断下自己的真正的源(比如socket)是否已经准备好,timeout是表示需要等待的超时时间。最终的超时时间将是所有源的最小时间。如果确定已经准备好了,则返回TRUE,表明后续就不用在poll中等待它了。
check函数:这个函数的存成,主要是prepare返回FALSE,而且poll也没等到,所以只能在Poll后再由程序自己去确定是否发生了啥时间。
dispatch函数:处理prepare或check函数返回TRUE的事件。这两个函数有一个返回TRUE就表明有东西要处理了。
应该查看代码确定下,是不是prepare返回TRUE的话,就不调用poll和check了。
经查,check只对非READY状态的source进行调用。
prepare本身没干什么,好像还是会调用poll。
最终调用dispatch的就是那些返回READY的句柄
2.2函数API
l main_loop相关函数,主线程有一个默认的context
l main_context相关函数,中断wait—g_main_context_wakeup
(1) context的所有权,只有线程得到context的所有权后,才能处理它。想想也是。
(2 )g_main_context_push_thread_default系列函数,干嘛使的?看了下实现,才知道每个线程都有一个私有数据队列,这个队列stack存的就是context。而g_main_context_default返回的是主线程的那个context(其实是一个全局变量的context。个人感觉一般不要操作这个mainloop。或者只由主线程去操作它。)
l 事件优先级define
l 空闲事件源,得先创建一个,然后插入到context中去。回调函数的返回值决定下次是否继续调用。
l 定时事件源。同上。
另外,关于source的创建及其使用,感觉怪怪的。只能attach,没有detach,而且只有destroy后不能再attach。why?
另外,g_source_new这个函数特别奇怪,返回的是GSource*,为何又要我传入GSource结构的大小?难度自己不知道吗?看了看实现,也没什么区别呀。或许可以从GSource中派生?
一个source里边含一个fd队列,所以new出来这个source对象实际是一个简单的封装,后续还是要把真正的fd加入到这个队列中去。
这么看来,如果要使用source干点活的话,还是得直接从I/O通道中创建source。
2.3 线程和线程池,异步队列
要在glib中使用线程,必须先调用g_thread_init来初始化线程库。
这个要求挺奇怪,里边肯定是有什么东西是全局的,所以要先初始化一下。
线程及同步结构没什么太多神奇的地方,一定是对POSIX同步结构进行了一下封装罢了。有些静态对象的使用,不需要初始化线程库也行。关键是用法要习惯。
线程池是一个有点奇怪的地方,主要是它的用法和参数上。
l 先创建一个线程池对象,里边会启动线程(可能)。叫任务池可能更好一点。
l 任务参数比较奇怪,有两个,一个是任务本身,另外一个是user数据。后来查看了下代码,通过g_thread_pool_push等加入的是任务,而用户数据则是在创建pool的时候传入的,所以对一个任务执行来说,有两个参数。其实里边就是一个list,把任务数据保存在list中罢了
异步队列,是一个比较实际的东西。原理很简单,有线程往里边加东西,另外有线程往里边取数据,这里围绕队列进行了同步处理。所以叫异步队列。
2.4 动态加载
动态加载需要包含gmodule.h,编译的时候也需要指定gmodule-2.0。
动态加载对这个路径分隔和模块后缀进行了宏定义,在不同平台下用一个宏即可。
有一个比较有意思的地方,在UNIX平台下,就是动态加载时候的CheckInitFunc和Unloadfunc。这两个函数是在加载动态库前和卸载前调用的最后一个函数。其实就是模仿Windows下的Dllmain函数。
l 内部处理经过勘察代码发现,CheckInitFunc是实际已经加载了,然后再调这个函数(所以模块必须实现一个叫g_module_check_init的函数)。这个典型的模仿DllMain
l UnLoadFunc是一样的道理。
G_MODULE_IMPORT和G_MODULE_EXPORT在Win下有意义。
2.5 内存分配和释放
这节的内容可能是经常要用到的。glib对一些C库函数进行了下封装吧应该。
g_malloc,g_malloc0,g_new,g_new0好像都没什么区别。
注意:一旦分配没成功,程序将直接exit。这点和库函数不一样。
晕,如果运行时候,出现这种问题也够可以的了。
2.6 I/O通道
分出来一个GIOChannel结构。实际代表一个FD,但是和source不太一样,source是事件意义上的源,而IO则是对FD的封装,这两个还是有本质区别的。
GIOChannel可以转换成一个source,也可以加入到context中,但只能是默认的全局context。
g_io_add_watcher,这个函数照实奇怪,为何不把context指针传进去啊,这样我就能加入到我想加入的context中去了。其实内部就是调用g_source_attach。非常奇怪。