关于glib的一些知识记录

关于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


基于GMainloop的GThread创建、退出与资源释放

_priv->context  =   g_main_context_new();    \\创建一个GMainContext

_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的主要部件是GMainContextGMainContext可以在多个GMainLoop间共享,但要求这些GMainLoop都在同一个线程中运行,前面提到的模态对话框就属于这一类。GMainContext通常由多个GSource组成,GSource是事件源的抽象,任何事件源,只要实现GSource规定的接口,都可以挂到GMainContext中来。

 

GSource的接口函数有:

1.        gboolean (*prepare)  (GSource    *source, gint       *timeout_);进入睡眠之前,在g_main_context_prepare里,mainloop调用所有Sourceprepare函数,计算最小的timeout时间,该时间决定下一次睡眠的时间。

2.        gboolean (*check)    (GSource    *source); poll被唤醒后,在g_main_context_check里,mainloop调用所有Sourcecheck函数,检查是否有Source已经准备好了。如果poll是由于错误或者超时等原因唤醒的,就不必进行dispatch了。

3.        gboolean (*dispatch) (GSource*source, GSourceFunc callback,gpointer user_data);当有Source准备好了,在g_main_context_dispatch里,mainloop调用所有Sourcedispatch函数,去分发消息。

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管道实现的,mainlooppoll时,它除了等待所有的Source外,还会等待wake_up_pipe管道。要唤醒poll,调用g_main_context_wakeup_unlockedwake_up_pipe里写入字母A就行了。


 http://club.cqvip.com/showtopic-666157-6.aspx

从文章中摘出来一些,然后自己整理了一下。

GLib 实现了一个功能强大的事件循环分发处理机制,GLib 内部实现了三种类型的事件源,分别是 Timeout, Idle, Child Watch。

同时也支持创建自定义的事件源——也就是添加child watch

李先静老师的FTK就是仿照glib的事件源来实现的

1. Timeout事件源

//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

 

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 : 当事件源被移除时被调用。

#include <glib.h>

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接口的代码模型片段。

 

[cpp]  view plain  copy
 print ?
  1. ...  
  2. struct pollfd fds[2];  
  3. int timeout_msecs = 500;  
  4. int ret;  
  5. int i;  
  6. /* Open STREAMS device. */  
  7. fds[0].fd = open("/dev/dev0", ...);  
  8. fds[1].fd = open("/dev/dev1", ...);  
  9. fds[0].events = POLLOUT | POLLWRBAND;  
  10. fds[1].events = POLLOUT | POLLWRBAND;  
  11. while(1) {  
  12.     ret = poll(fds, 2, timeout_msecs);  
  13.     if (ret > 0) {  
  14.         /* An event on one of the fds has occurred. */  
  15.         for (i=0; i<2; i++) {  
  16.         if (fds[i].revents & POLLWRBAND) {  
  17.         /* Priority data may be written on device number i. */  
  18.         ...  
  19.         }  
  20.         if (fds[i].revents & POLLOUT) {  
  21.         /* Data may be written on device number i. */  
  22.         ...  
  23.         }  
  24.         if (fds[i].revents & POLLHUP) {  
  25.         /* A hangup has occurred on device number i. */  
  26.         ...  
  27.         }  
  28.         }  
  29.     }  
  30. }  
  31. ...  
 

 

 

上面这个代码,我们可以把它拆分成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这个标记变量来标识的。

 

 

[cpp]  view plain  copy
 print ?
  1. void g_main_loop_run (GMainLoop *loop)  
  2. {  
  3.     GThread *self = G_THREAD_SELF;  
  4.     g_return_if_fail (loop != NULL);  
  5.     g_return_if_fail (g_atomic_int_get (&loop->ref_count) > 0);  
  6.     g_atomic_int_inc (&loop->ref_count);  
  7.     loop->is_running = TRUE;  
  8.     while (loop->is_running)  
  9.        g_main_context_iterate (loop->context, TRUE, TRUE, self);  
  10.     UNLOCK_CONTEXT (loop->context);  
  11.     g_main_loop_unref (loop);  
  12. }  
  13. static gboolean g_main_context_iterate(GMainContext *context, gboolean block,  
  14.         gboolean dispatch, GThread *self) {  
  15.     gint max_priority;  
  16.     gint timeout;  
  17.     gboolean some_ready;  
  18.     gint nfds, allocated_nfds;  
  19.     GPollFD *fds = NULL;  
  20.     UNLOCK_CONTEXT (context);  
  21.     if (!context->cached_poll_array) {  
  22.         context->cached_poll_array_size = context->n_poll_records;  
  23.         context->cached_poll_array = g_new (GPollFD, context->n_poll_records);  
  24.     }  
  25.     allocated_nfds = context->cached_poll_array_size;  
  26.     fds = context->cached_poll_array;  
  27.     UNLOCK_CONTEXT (context);  
  28.     g_main_context_prepare(context, &max_priority);  
  29.     while ((nfds = g_main_context_query(context, max_priority, &timeout, fds,  
  30.             allocated_nfds)) > allocated_nfds) {  
  31.         LOCK_CONTEXT (context);  
  32.         g_free(fds);  
  33.         context->cached_poll_array_size = allocated_nfds = nfds;  
  34.         context->cached_poll_array = fds = g_new (GPollFD, nfds);  
  35.         UNLOCK_CONTEXT (context);  
  36.     }  
  37.     if (!block)  
  38.         timeout = 0;  
  39.     g_main_context_poll(context, timeout, max_priority, fds, nfds);  
  40.     some_ready = g_main_context_check(context, max_priority, fds, nfds);  
  41.     if (dispatch)  
  42.         g_main_context_dispatch(context);  
  43.     
  44.     LOCK_CONTEXT (context);  
  45.     return some_ready;  
  46. }  
 

 

 

仔细看一下g_main_context_iterate()函数,也可以把它划分成3个部分,和前面代码片段的3部分对应上。

1. 第一部份,准备要检测的文件集合

 

[cpp]  view plain  copy
 print ?
  1. g_main_context_prepare(context, &max_priority);  
  2. while ((nfds = g_main_context_query(context, max_priority, &timeout, fds,  
  3. allocated_nfds)) > allocated_nfds) {  
  4. LOCK_CONTEXT (context);  
  5. g_free(fds);  
  6. context->cached_poll_array_size = allocated_nfds = nfds;  
  7. context->cached_poll_array = fds = g_new (GPollFD, nfds);  
  8. UNLOCK_CONTEXT (context);  
  9. }  
 

首先是调用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,等待事件发生。

[cpp]  view plain  copy
 print ?
  1. if (!block)  
  2.         timeout = 0;  
  3.     g_main_context_poll(context, timeout, max_priority, fds, nfds);  
 

就是调用g_main_context_poll(context, timeout, max_priority, fds, nfds),g_main_context_poll只是对poll函数的一个简单封装。

 

3. 第三部分,遍历文件集合(struct pollfd结构体的集合),执行对应的操作。

[cpp]  view plain  copy
 print ?
  1.       some_ready = g_main_context_check(context, max_priority, fds, nfds);  
  2. f (dispatch)  
  3. g_main_context_dispatch(context);  
 

 

通常的想法,可能会是这种伪代码形式(这种形式也和前面代码片段的形式是一致的)

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的源代码实现,这样才有助于理解。


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值