`

Glib学习笔记(2)

 
阅读更多

2.1 MainLoop

main loopGlib中一个非常重要的部分,其主要用途类似于Windows的消息循环。所以它是一个循环,不停得从某个地方取得“消息”,然后派发到消息处理函数,交给他们处理。

从执行者角度看,派发到消息处理函数实际还是消息循环所在的线程调用对应的函数。

Glib中,消息循环对应的就是这个mainLoop。消息队列没有对应物。但是消息的产地由GSource标示,例如一个文件描述符,一个socket都是某种sourceGlib提供了GMainContext来管理这些source。而mainLoop只要管理Context即可管理很多source

其实很简单,仔细比较下windows下的线程函数就可知道。Windows下的线程函数一般会循环等待一些事件,例如

while(TRUE)

{

WaitForMultiObjects(count,arry,...);

其中arry是一个事件数组。

}

仔细想想,mainLoop是上面那个while循环的对象封装,contextarry数组的对象封装,而arry里边的成员就是source的对象封装。

通过这种方式的对象封装,在不同线程里边直接使用mainLoop对象,或者切换context对象就可以做到非常方便简洁。

循环退出,必须有个事件处理中调用g_main_loop_quit才可以。

如果你能这么理解MainLoop的话,就非常容易从WIN-API转到GLib中去了。

实际上,MainLoop的整个系统比较精巧,涉及到定时源,空闲源,优先级等。

我看了下Glib的实行代码,内部对应WaitForMultiObjects的是poll函数。可想,如果有很多事件的话,由poll本身造成的效率损失将比较大。另外它是在一个线程中执行处理函数的,所以会影响后续事件的处理。

下面将详细讲述和比较。

1.迭代

其实就是强制调用一次WaitForMultiObjects以检查发生了什么事件。Glibcontext分了好几种状态。

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函数:处理preparecheck函数返回TRUE的事件。这两个函数有一个返回TRUE就表明有东西要处理了。

应该查看代码确定下,是不是prepare返回TRUE的话,就不调用pollcheck了。

经查,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后不能再attachwhy

另外,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平台下,就是动态加载时候的CheckInitFuncUnloadfunc。这两个函数是在加载动态库前和卸载前调用的最后一个函数。其实就是模仿Windows下的Dllmain函数。

l 内部处理经过勘察代码发现,CheckInitFunc是实际已经加载了,然后再调这个函数(所以模块必须实现一个叫g_module_check_init的函数)。这个典型的模仿DllMain

l UnLoadFunc是一样的道理。

G_MODULE_IMPORTG_MODULE_EXPORTWin下有意义。

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。非常奇怪。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics