关于glib函数的网站
http://web.mit.edu/barnowl/share/gtk-doc/html/glib/glib-The-Main-Event-Loop.html
这个网站更全点https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html
_priv->mainloop = g_main_loop_new( _priv->context, FALSE ); \\创建一个GMainloop
_priv->thread = g_thread_new( "thread", threadEntry, NULL ); \\创建一个GThread
2. 线程入口
g_main_loop_run( _priv->mainloop ); \\主要实现,线程等待GSource事件
3. 线程退出与资源释放
在需要终止线程的地方调用如下代码
g_main_loop_quit( _priv->mainloop ); \\GMainloop退出
g_thread_join( _priv->thread ); \\重要,等待线程成功退出,glib内部会对GThread的reference count减1,线程资源才会释放
g_main_loop_unref( _priv->mainloop ); \\释放之前创建的GMainloop
g_main_context_unref( _priv->context ); \\释放之前创建的GMainContext
main loop使用模式大致如下:
loop = g_main_loop_new (NULL, TRUE); g_main_loop_run (loop); |
g_main_loop_new创建一个main loop对象,一个main loop对象只能被一个线程使用,但一个线程可以有多个main loop对象。在GTK+应用中,一个线程使用多个main loop的主要用途是实现模态对话框,它在gtk_dialog_run函数里创建一个新的main loop,通过该main loop分发消息,直到对话框关闭为止。
g_main_loop_run则是进入主循环,它会一直阻塞在这里,直到让它退出为止。有事件时,它就处理事件,没事件时就睡眠。
g_main_loop_quit则是用于退出主循环,相当于Win32下的PostQuitMessage函数。
Glib main loop的最大特点就是支持多事件源,使用非常方便。来自用户的键盘和鼠标事件、来自系统的定时事件和socket事件等等,还支持一个称为idle的事件源,其主要用途是实现异步事件。Main loop的基本组成如下图所示:
GMainLoop的主要部件是GMainContext,GMainContext可以在多个GMainLoop间共享,但要求这些GMainLoop都在同一个线程中运行,前面提到的模态对话框就属于这一类。GMainContext通常由多个GSource组成,GSource是事件源的抽象,任何事件源,只要实现GSource规定的接口,都可以挂到GMainContext中来。
GSource的接口函数有:
1. gboolean (*prepare) (GSource *source, gint *timeout_);进入睡眠之前,在g_main_context_prepare里,mainloop调用所有Source的prepare函数,计算最小的timeout时间,该时间决定下一次睡眠的时间。
2. gboolean (*check) (GSource *source); poll被唤醒后,在g_main_context_check里,mainloop调用所有Source的check函数,检查是否有Source已经准备好了。如果poll是由于错误或者超时等原因唤醒的,就不必进行dispatch了。
3. gboolean (*dispatch) (GSource*source, GSourceFunc callback,gpointer user_data);当有Source准备好了,在g_main_context_dispatch里,mainloop调用所有Source的dispatch函数,去分发消息。
4. void (*finalize) (GSource *source);在Source被移出时,mainloop调用该函数去销毁Source。
Main loop的工作流程简图如下
下面我们看看几个内置Source的实现机制:
Idle它主要用实现异步事件,功能类似于Win32下的PostMessage。但它还支持重复执行的特性,根据用户注册的回调函数的返回值而定。
1. g_idle_prepare把超时设置为0,也就是即时唤醒,不进入睡眠状态。
2. g_idle_check始终返回TRUE,表示准备好了。
3. g_idle_dispatch调用用户注册的回调函数。
Timeout它主要用于实现定时器,支持一次定时和重复定时,根据用户注册的回调函数的返回值而定。
1. g_timeout_prepare计算下一次的超时时间。
2. g_timeout_check检查超时时间是否到了,如果到了就返回TRUE,否则返回FALSE。
3. g_timeout_dispatch调用用户注册的回调函数。
线程可以向自己的mainloop中增加Source,也可以向其它线程的mainloop增加Source。向自己的mainloop中增加Source时,mainloop已经唤醒了,所以不会存在什么问题。而向其它线程的mainloop增加Source时,对方线程可能正挂在poll里睡眠,所以要想法唤醒它,否则Source可能来不及处理。在Linux下,这是通过wake_up_pipe管道实现的,mainloop在poll时,它除了等待所有的Source外,还会等待wake_up_pipe管道。要唤醒poll,调用g_main_context_wakeup_unlocked向wake_up_pipe里写入字母A就行了。
http://club.cqvip.com/showtopic-666157-6.aspx
从文章中摘出来一些,然后自己整理了一下。
GLib 实现了一个功能强大的事件循环分发处理机制,GLib 内部实现了三种类型的事件源,分别是 Timeout, Idle, Child Watch。
同时也支持创建自定义的事件源——也就是添加child watch
李先静老师的FTK就是仿照glib的事件源来实现的
1. Timeout事件源
#include<glib.h>
GMainLoop* loop;
gint counter = 10;
gboolean callback(gpointer arg)
{
g_print(".");
if(--counter ==0){
g_print("\n");
//退出循环
g_main_loop_quit(loop);
//注销定时器
return FALSE;
}
//定时器继续运行
return TRUE;
}
int main(int argc, char* argv[])
{
if(g_thread_supported() == 0)
g_thread_init(NULL);
g_print("g_main_loop_new\n");
loop = g_main_loop_new(NULL, FALSE);
//增加一个定时器,100毫秒运行一次callback
g_timeout_add(100,callback,NULL);
g_print("g_main_loop_run\n");
g_main_loop_run(loop);
g_print("g_main_loop_unref\n");
g_main_loop_unref(loop);
return 0;
}
编译运行:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop1.c -o mainloop1
2. Idle事件源
已经在另一篇博客中介绍了
3. Child Watch——系统定义的事件源
#include <glib.h>
#include <stdio.h>
#include <strings.h>
GMainLoop* loop;
//当stdin有数据可读时被GSource调用的回调函数
gboolean callback(GIOChannel *channel)
{
gchar* str;
gsize len;
//从stdin读取一行字符串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);
//去掉回车键()
while(len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))
str[--len]='\0';
//反转字符串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("\n");
//判断结束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}
void add_source(GMainContext *context)
{
GIOChannel* channel;
GSource* source;
//这里我们监视stdin是否可读, stdin的fd默认等于1
channel = g_io_channel_unix_new(1);
//g_io_create_watch创建一个默认的io监视作用的GSource,下次再研究自定义GSource。参数G_IO_IN表示监视stdin的读取状态。
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);
//设置stdin可读的时候调用的回调函数
g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);
//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}
int main(int argc, char* argv[])
{
GMainContext *context;
if(g_thread_supported() == 0)
g_thread_init(NULL);
//新建一个GMainContext
context = g_main_context_new();
//然后把GSource附到这个Context上
add_source(context);
//把Context赋给GMainLoop
loop = g_main_loop_new(context, FALSE);
g_print("input string('q' to quit)\n");
g_main_loop_run(loop);
g_main_loop_unref(loop);
//Context用完计数器减1
g_main_context_unref(context);
return 0;
}
4. Child Watch——自定义事件源
GSource * g_source_new(GSourceFuncs * source_funcs, guint struct_size);
这个函数用于创建一个自定义事件源,新的事件源可以使用 g_source_attach() 函数加入到主循环上下文中。
source_funcs : 包含用于实现事件行为的函数的结构
struct_size : 创建的 GSource 结构大小,不能小于 sizeof(GSource)
返回值 : 返回新创建的 GSource
创建一个新的事件源包含用于实现事件行为的函数的结构体。
prepare : 设置检查事件时间超时。如果返回 TRUE, check 会立刻被调用;如果返回 FALSE 并设置了 timeout , timeout 时间后 check 会被调用。
check : 检查事件是否准备完毕。返回 TRUE 为准备完毕, dispatch 会被立刻调用;返回 FALSE 不调用 dispatch,进入下一次事件循环。
dispatch : 分发事件。返回 TRUE 将继续下一次操作循环;返回 FALSE 中止本事件源的事件循环。
finalize : 当事件源被移除时被调用。
gboolean source_prepare_cb(GSource * source,
gint * timeout)
{
g_printf("prepare\n");
*timeout = 1000;
return FALSE;
}
gboolean source_check_cb(GSource * source)
{
g_printf("check\n");
return TRUE;
}
gboolean source_dispatch_cb(GSource * source,
GSourceFunc callback, gpointer data)
{
g_printf("dispatch\n");
return TRUE;
}
void source_finalize_cb(GSource * source)
{
g_printf("finalize\n");
}
int main(int argc, char * argv[])
{
GMainLoop * mainloop;
GMainContext * maincontext;
GSource * source;
GSourceFuncs sourcefuncs;
sourcefuncs.prepare = source_prepare_cb;
sourcefuncs.check = source_check_cb;
sourcefuncs.dispatch = source_dispatch_cb;
sourcefuncs.finalize = source_finalize_cb;
mainloop = g_main_loop_new(NULL, FALSE);
maincontext = g_main_loop_get_context(mainloop);
source = g_source_new(&sourcefuncs, sizeof(GSource));
g_source_attach(source, maincontext);
g_main_loop_run(mainloop);
return 0;
}
注意prepare 中会返回一个超时时间
g_main_loop_new
按着glib的文档顺序,先来看看事件循环吧。
从最简单的例子开始:
//mainloop0.c
#include<glib.h>
GMainLoop* loop;
int main(int argc, char* argv[])
{
//g_thread_init是必需的,GMainLoop需要gthread库的支持。
if(g_thread_supported() == 0)
g_thread_init(NULL);
//创建一个循环体,先不管参数的意思。
g_print("g_main_loop_new/n");
loop = g_main_loop_new(NULL, FALSE);
//让这个循环体跑起来
g_print("g_main_loop_run/n");
g_main_loop_run(loop);
//循环运行完成后,计数器减一
//glib的很多结构类型和c++的智能指针相似,拥有一个计数器
//当计数器为0时,自动释放资源。
g_print("g_main_loop_unref/n");
g_main_loop_unref(loop);
return 0;
}
好了,现在编译:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop0.c -o mainloop0
然后运行:
./mainloop0
你会发现程序会在g_main_loop_run函数阻塞,这就是glib main loop了,如果没有人通知它退出,它是不会退出的。
通知循环退出的函数是g_main_loop_quit。
怎么通知呢?主线程被g_main_loop_run阻塞了,没办法运行quit。本来我准备开一个线程,sleep一秒钟,然后调用g_main_loop_quit。不过一想我们都在学习高精尖武器了,还用土枪土炮干啥。使用glib的定时器吧~
//mainloop1.c
#include<glib.h>
GMainLoop* loop;
gint counter = 10;
gboolean callback(gpointer arg)
{
g_print(".");
if(--counter ==0){
g_print("/n");
//退出循环
g_main_loop_quit(loop);
//注销定时器
return FALSE;
}
//定时器继续运行
return TRUE;
}
int main(int argc, char* argv[])
{
if(g_thread_supported() == 0)
g_thread_init(NULL);
g_print("g_main_loop_new/n");
loop = g_main_loop_new(NULL, FALSE);
//增加一个定时器,100毫秒运行一次callback
g_timeout_add(100,callback,NULL);
g_print("g_main_loop_run/n");
g_main_loop_run(loop);
g_print("g_main_loop_unref/n");
g_main_loop_unref(loop);
return 0;
}
编译运行:
gcc -g `pkg-config --cflags --libs glib-2.0 gthread-2.0` mainloop1.c -o mainloop1
./mainloop1
Yeah!一秒钟后,程序正常退出了!定时器好简单!
今天到此为止。最后思考一个问题,glib的main loop主要提供给gtk使用,是gtk界面事件循环的基础,这是无可非议的。但是,在别的地方,比如我们普通的console、service程序中,有必要用main loop么?main loop还能够应用在哪些场合?
-----------------------
回到前一天的问题,除了交互性界面程序,还有哪些地方适合使用glib的event loop呢?我认为答案应该是,所有需要异步操作的地方都可以用event loop。像文件、管道、设备、socket、timer、idle和其他自定义的事件都可以产生event.
要让GMainLoop能够处理这些类型的event,首先就必须把它们加到GMainLoop去。
首先我们需要了解event loop的这三个基本结构:GMainLoop, GMainContext和GSource。
它们之间的关系是这样的:
GMainLoop -> GMainContext -> {GSource1, GSource2, GSource3......}
每个GmainLoop都包含一个GMainContext成员,而这个GMainContext成员可以装各种各样的GSource,GSource则是具体的各种Event处理逻辑了。在这里,可以把GMainContext理解为GSource的容器。(不过它的用处不只是装GSource)
创建GMainLoop使用函数g_main_loop_new, 它的第一个参数就是需要关联的GMainContext,如果这个值为空,程序会分配一个默认的Context给GMainLoop。
把GSource加到GMainContext呢,则使用函数g_source_attach。
接下来看这个例子,它的作用是从stdin读取字符串,然后反转字符串并输出到屏幕。
//mainloop2.c
#include <glib.h>
#include <stdio.h>
#include <strings.h>
GMainLoop* loop;
//当stdin有数据可读时被GSource调用的回调函数
gboolean callback(GIOChannel *channel)
{
gchar* str;
gsize len;
//从stdin读取一行字符串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);
//去掉回车键()
while(len > 0 && (str[len-1] == '/r' || str[len-1] == '/n'))
str[--len]='/0';
//反转字符串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("/n");
//判断结束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}
void add_source(GMainContext *context)
{
GIOChannel* channel;
GSource* source;
//这里我们监视stdin是否可读, stdin的fd默认等于1
channel = g_io_channel_unix_new(1);
//g_io_create_watch创建一个默认的io监视作用的GSource,下次再研究自定义GSource。参数G_IO_IN表示监视stdin的读取状态。
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);
//设置stdin可读的时候调用的回调函数
g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);
//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}
int main(int argc, char* argv[])
{
GMainContext *context;
if(g_thread_supported() == 0)
g_thread_init(NULL);
//新建一个GMainContext
context = g_main_context_new();
//然后把GSource附到这个Context上
add_source(context);
//把Context赋给GMainLoop
loop = g_main_loop_new(context, FALSE);
g_print("input string('q' to quit)/n");
g_main_loop_run(loop);
g_main_loop_unref(loop);
//Context用完计数器减1
g_main_context_unref(context);
return 0;
}
1. 之前提到过,GSource和被管理的文件的对应关系,不是 1对1,而是 1对n,这个n,甚至可以是0。这个的意思就是说,一个GSource,可以对应(管理)一个文件描述符,也可以同时对应(管理)多个文件描述符,也可以一个都不管理。
glib中已经提供了几个GSource的子类,其中的g_timeout_source_new(guint interval)和g_idle_source_new(void)这两个函数构造出的GSource子类,就并不使用文件描述符号。
GTimeoutSource,只需要保存设定的间隔时间,在poll轮循的prepare和check阶段,会去检查设定的这个间隔时间是否到达,如果到达设定的时间,则它的dispatch函数会被调用。
g_idle_source_new(void)其实都没有继承GSource,它也不需要和文件绑定,只不过它的优先级比较低,只有在poll轮询的空闲阶段,它的dispatch函数会被调用。而它的prepare和check函数,始终都是返回的TRUE。
2. GMainLoop怎样根据GSource的优先级进行调度?
在g_main_context_prepare()的时候,会从所有的GSource中找出最大的一个优先级,然后在g_main_context_query()的时候,只会把等于这个优先级的GSource对应的GPollFD添加到poll的监控集合中。基于这个优先级制度,GMainLoop就可以对GSource进行一个调度。以idle source为例,如果同时有一个GTimeoutSource和idle source,因为idle source的优先级更低,所有GMainLoop就不会把idle source添加到poll轮询的监控集合中,也就是说,直到GTimeoutSource从GMainLoop中移除,idle source的callback函数,才有机会被调用。
做Linux程序开发有一段时间了,也使用过好几个UI库,包括gtk,qt,还有clutter。其中感觉最神秘的,就是所谓的“主事件循环",在qt中,就是QApplication,gtk中是gtk_main(),clutter中则是clutter_main()。这些事件循环对象,都被封装的很“严密",使用的时候,代码都很简单。而我们在编写应用程序的过程中,通常也只需要重载widget的event处理函数(或者是处理event对应的信号),至于event是怎样产生和传递的,这就是个谜。
最近时间比较充裕,仔细研究了一下事件循环,参考的代码是glib中的GMainLoop。gtk_main()和clutter_main(),都是基于GMainLoop的。另外,其实事件循环的概念,也不仅仅使用在UI编程中,在网络编程中,同样大量的使用,可以这样说,event loop,是编程模型中,最基本的一个概念。可惜在大学教材中,从来没有看到过这个概念,玩单片机的时候,也用不到这个概念,只有在有操作系统的环境下,才会有event loop。
event loog的代码基础,还要用到一个概念--I/O的多路复用。目前常用的api接口,有3个,select,poll以及epoll。glib是一个跨平台的库,在linux上,使用的是poll函数,在window上,使用的是select。而epoll这个接口,在linux2.6中才正式推出,它的效率,比前两者更高,在网络编程中大量使用。而本质上,这三个函数,其实是相同的。
如果对I/O多路复用还不了解,请先自行google学习。下面,仅仅给出一个使用poll接口的代码模型片段。
上面这个代码,我们可以把它拆分成3部分:
1. 准备要检测的文件集合(不是简单的准备“文件描述符"的集合,而是准备struct pollfd结构体的集合。这就包括了文件描述符,以及希望监控的事件,如可读/可写/或可执行其他操作等)。
2. 执行poll,等待事件发生(文件描述符对应的文件可读/可写/或可执行其他操作等)或者是函数超时返回。
3. 遍历文件集合(struct pollfd结构体的集合),判断具体是哪些文件有“事件"发生,并且进一步判断是何种“事件"。然后,根据需求,执行对应的操作(上面的代码中,用...表示的对应操作)。
其中2和3对应的代码,都放在一个while循环中。而在3中所谓的“对应的操作",还可以包括一种“退出"操作,这样的话,就可以从while循环中退出,这样的话,整个进程也有机会正常结束。
再次提醒一下,请先把上面这段代码看懂,最好是有过实际的使用经验,这样更有助于理解。
下面开始讨论重点。这段代码仅仅是演示,所以它很简单。但是,从另外一个角度来看,这个代码片段又很死板,尤其是对于新手或者是没有I/O多路复用实际使用经验的朋友来说,很容易被这段代码模型“框住"。它还能变得更灵活吗?怎样才能变得更灵活?详细解释之前,先提几个小问题。
1. 前面的代码,仅打开了2个文件,并且传递给poll函数。如果,在程序运行过程中,想动态的增加或者删除poll函数监控的文件,怎么办?
2. 前面的代码,设置的超时时间,是固定的。假设,某个时刻,有100个文件需要被监控,而针对这100个不同的文件,每个文件期望设置的超时时间都不一样,怎么办?
3. 前面的代码,当poll函数返回,对文件集合进行遍历的时候,是逐个进行判断并且执行“对应的操作"。如果,有100个文件被监控,当poll返回时,这100个文件,都满足条件,可以进行“对应的操作",其中的50个文件的“对应的操作"很耗时间,但是并不是这么紧急(可以稍后再处理,比如等到下一轮poll返回时再处理),而另外50个文件的“对应的操作"需要立即执行,并且很快(在下一次poll的时候)又会有新的事件发生并且满足判断时的条件,怎么办?
对第1个问题,可以想到,需要对所有的文件(struct pollfd)做一个统一的管理,需要有添加和删除文件的功能。用面向对象的思想来看,这就是一个类,暂且叫做类A。
对第2个问题,可以想到,还需要对每一个被监控的文件(struct pollfd),做更多的控制。也可以用一个类来包装被监控的文件,对这个文件进行管理,在该对象中,包含了struct pollfd结构体,该类还可以提供对应的文件所期望的超时时间。暂且叫做类B。
对第3个问题,可以考虑为每一个被监控的文件设置一个优先级,然后就可以根据优先级优先执行更“紧急"的“对应的操作"。这个优先级信息,也可以存储在类B中。设计出了类B之后,类A就不再是直接统一管理文件了,而是变成统一管理类B,可以看成是类B的一个容器类。
有了这3个解答之后,就可以对这个代码片段添油加醋,重新组装,让它变得更灵活了。glib中的GMainLoop,做的就是这样的事情,而且,它做的事情,除了这3个解答中描述的内容外,还有更让人“吃惊的惊喜"。
:-),这里又要提醒一下了,下面将对GMainLoop进行描述,所以,最好是先使用一下GMainLoop,包括其中的g_timeout_source_new(guint interval),g_idle_source_new(void)以及g_child_watch_source_new(GPid pid)。顺便再强调一下,学习编程的最好的办法,就是看代码,而且是看高质量的代码。
后面的讲解,主要是从原理上来介绍GMainLoop的实现机制,并不是代码的情景分析。代码的详细阅读,还是需要自己老老实实的去实践的。后面的这些介绍,只是为了帮助大家更容易的理解源代码。
glib的主事件循环框架,由3个类来实现,GMainLoop,GMainContext和GSource,其中的GMainLoop仅仅是GMainContext的一个外壳,最重要的,还是GMainContext和GSource。GMainContext就相当于前面提到的类A,而GSource就相当于前面提到的类B。从原理上讲,g_main_loop_run(GMainLoop *loop)这个函数的内部实现,和前面代码片段中的while循环,是一致的。(还有一点要说明的,在多线程的环境下,GMainLoop的代码实现显得比较复杂,为了学习起来更容易些,可以先不考虑GMainLoop中线程相关的代码,这样的话,整体结构就和前面的代码片段是一致的。后面的讲解以及代码片段,都略去了线程相关的代码,这并不影响对event loop的学习和理解)。
1.GSource----GSource相当于前面提到的类B,它里面会保存优先级信息,同时,GSource要管理对应的文件(保存struct pollfd结构体的指针,而且是以链表的形式保存),而且,GSource和被管理的文件的对应关系,不是 1对1,而是 1对n,这个n,甚至可以是0(这就是一个“吃惊的惊喜",后面会有更详细的解释)。GSource还必须提供3个重要的函数(从面向对象的角度看,GSource是一个抽象类,而且有三个重要的纯虚函数,需要子类来具体实现),这3个函数就是:
gboolean (*prepare) (GSource *source, gint *timeout_);
gboolean (*check) (GSource *source);
gboolean (*dispatch) (GSource *source, GSourceFunc callback, gpointer user_data);
再看一下前面代码片段中的3部分,这个prepare函数,就是要在第一部分被调用的,check和dispathch函数,就是在第3部分被调用的。有一点区别是,prepare函数,也要放到while循环中,而不是在循环之外(因为要动态的增加或者删除poll函数监控的文件)。
prepare函数,会在执行poll之前被调用。该GSource中的struct pollfd是否希望被poll函数监控,就由prepare函数的返回值来决定,同时,该GSource希望的超时时间,也由参数timeout_返回。
check函数,在执行poll之后被调用。该GSource中的struct pollfd是否有事件发生,就由check函数的返回值来描述(在check函数中可以检测struct pollfd结构体中的返回信息)。
dispatch函数,在执行poll和check函数之后被调用,并且,仅当对应的check函数返回true的时候,对应的dispatch函数才会被调用,dispatch函数,就相当于“对应的操作"。
2.GMainContext----GMainContext是GSource的容器,GSource可以添加到GMainContext里面(间接的就把GSource中的struct pollfd也添加到GMainContext里面了),GSource也可以从GMainContext中移除(间接的就把GSource中的struct pollfd从GMainContext中移除了)。GMainContext可以遍历GSource,自然就有机会调用每个GSource的prepare/check/dispatch函数,可以根据每个GSource的prepare函数的返回值来决定,是否要在poll函数中,监控该GSource管理的文件。当然可以根据GSource的优先级进行排序。当poll返回后,可以根据每个GSource的check函数的返回值来决定是否需要调用对应的dispatch函数。
下面给出关键的代码片段,其中的g_main_context_iterate()函数,就相当于前面代码片段中的循环体中要做的动作。循环的推出,则是靠loop->is_running这个标记变量来标识的。
仔细看一下g_main_context_iterate()函数,也可以把它划分成3个部分,和前面代码片段的3部分对应上。
1. 第一部份,准备要检测的文件集合
首先是调用g_main_context_prepare(context, &max_priority),这个就是遍历每个GSource,调用每个GSource的prepare函数,选出一个最高的优先级max_priority,函数内部其实还计算出了一个最短的超时时间。
然后调用g_main_context_query,其实这是再次遍历每个GSource,把优先级等于max_priority的GSource中的struct pollfd,添加到poll的监控集合中。
这个优先级,也是一个“吃惊的惊喜"。按照通常的想法,文件需要被监控的时候,会立刻把它放到监控集合中,但是有了优先级这个概念后,我们就可以有一个“隐藏的后台任务",g_idle_source_new(void)就是最典型的例子。
2. 第二部份,执行poll,等待事件发生。
就是调用g_main_context_poll(context, timeout, max_priority, fds, nfds),g_main_context_poll只是对poll函数的一个简单封装。
3. 第三部分,遍历文件集合(struct pollfd结构体的集合),执行对应的操作。
通常的想法,可能会是这种伪代码形式(这种形式也和前面代码片段的形式是一致的)
foreach(all_gsouce) {
if (gsourc->check) {
gsource->dispatch();
}
}
实际上,glib的处理方式是,先遍历所有的GSource,执行g_main_context_prepare(context, &max_priority),调用每个GSource的check函数,然后把满足条件的GSource(check函数返回true的GSource),添加到一个内部链表中。
然后执行g_main_context_dispatch(context),遍历刚才准备好的内部链表中的GSource,调用每个GSource的dispatch函数。
ok,分析到此结束,总结一下,重点,首先是要先理解poll函数的使用方法,建立I/O多路复用的概念,然后,建议看一下GMainContext的源代码实现,这样才有助于理解。