GMainLoop的实现原理和代码模型

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

转载时请注明出处和作者联系方式
文章出处:http://blog.csdn.net/jack0106 
作者联系方式:冯牮 fengjian0106@yahoo.com.cn

 

 

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

 

 

GLib/Gstreamer代码浅析 阅读详情

相关推荐

glib主事件循环

glib 主事件循环

weixin_42834523的博客 1355

Glib学习(22) 主事件循环 The Main Event Loop

glib源码下载:http://ftp.gnome.org/pub/gnome/sources/glib/ glib帮助文档:https://developer.gnome.org/glib/ 主事件循环 - 管理所有可用的事件源 Includes #include <glib.h> 描述 主事件循环管理GLibGTK +应用程序的所有可用事件源。 这些事件可以来自任意数量的不同...

andylauren的专栏 8234

Glib学习(2)——main loop

文章目录简介包含文件类型几个函数介绍GThreadFunc () 简介 主事件循环——管理所有可用的事件源 主事件循环管理GLibGTK+应用程序的所有可用事件源。这些事件可以来自任意数量的不同类型的源,比如文件描述符(纯文件、管道或套接字)超时。还可以使用g_source_attach()添加新类型的事件源。 为了允许在不同的线程中处理多个独立的源集,每个源都与一个GMainContext相关联。GMainContext只能在单个线程中运行,但是可以向其添加源,并从其他线程中删除源。所有在GMainC

MoL的博客 2476

glib--消息分发(gmainloop,gsource),信号

每一个管道默认包含一个总线,所以应用程序不需要再创建总线。 应用程序只需要在总线上设置一个类似于对象的信号处理器的消息处理器。 当主循环运行的时候,总线将会轮询这个消息处理器是否有新的消息,当消息被采集到后,总线将呼叫相应的回调函数来完成任务。  GLib对核心应用的支持包括事件循环、内存操作、线程操作、动态链接库的操作出错处理与日志等。基本上,所有需要异步操作的地方都可以用事件循环。像文件、...

evsqiezi 5739

【glib】GMainLoop实现原理代码模型

本文主要通过Glib中的GMainloop介绍事件循环(event loop)

EthanYYYY的博客 3791

GMainLoop中的GSource

<br />转载时请注明出处作者联系方式<br />文章出处:http://blog.csdn.net/jack0106 <br />作者联系方式:冯牮 fengjian0106@yahoo.com.cn<br /> <br /><br />上一篇文章GMainLoop实现原理代码模型里面,介绍了GMainLoop代码模型,其中的一些内容,还可以进一步讨论一下。<br /> <br />1. 之前提到过,GSource被管理的文件的对应关系,不是 1对1,而是 1对n,这个n,甚至可以是0。这个的

Embedded Linux Development 3860

GMainLoop, GMainContextGSource学习笔记

做过GUI开发相关工作的人的人都知道,很多应用程序都是事件驱动的。这些事件大部分都来自于用户,比如键盘事件、鼠标事件或遥控器事件。还有一些事件来自于系统内部,比如定时事件、socket事件其它文件事件等等。在没有任何事件的情况下,应用程序处于睡眠状态。     因为这种事件驱动机制,GUI应用程序都毫无例外的需要一个主循环(main loop)。主循环(main loop)控制应用程序什么

铁匠Smith先生的专栏 9037

GMainLoop的原型

转载时请注明出处作者联系方式 文章出处:http://blog.csdn.net/jack0106  作者联系方式:冯牮 fengjian0106@yahoo.com.cn

1313

GTK 主循环 main loop 的工作原理

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GTK+主循环(mai

jggyff的博客 353

GTK+主循环(main loop)的工作原理

GTK+主循环(main loop)的工作原理 转载时请注明出处作者联系方式:http://blog.csdn.net/absurd 作者联系方式:Li XianJing &lt;xianjimli at hotmail dot com&gt; 更新时间:2007-3-17 我们知道GUI应用程序都是事件驱动的。这些事件大部分都来自于用户,比如键盘事件、鼠标事件或笔点事件。还有一...

jinghuainfo 191

glib main loop 笔记

 从最简单的例子开始://mainloop0.c#include GMainLoop* loop;int main(int argc, char* argv[]){    //g_thread_init是必需的,GMainLoop需要gthread库的支持。    if(g_thread_supported() == 0)        g_thread_init(NULL);    //创建一个

e28sean的专栏 2032

modal dialog 的实现; mainloop的嵌套; g_object_notify的使用方法

modal dialog 的实现gtk中,modal dialog的实现一般是说通过g_main_loop的嵌套来实现的。但最近发现并非如此。是否为modal dialog, 主要由是否调用如下函数控制: 1、gtk_window_set_modal(window, TRUE);而这个函数主要做几件事情: (1)、向window manager设置_NET_WM_STATE ato

hwizhao的专栏 2697

GSource

GMainLoop的主要部件是GMainContext,GMainContext可以在多个GMainLoop间共享, ​但要求这些GMainLoop都在同一个线程中运行,前面提到的模态对话框就属于这一类。 ​GMainContext通常由多个GSource组成,GSource是事件源的抽象, ​任何事件源,只要实现GSource规定的接口,都可以挂到GMainContext中来。 GSource的接口函数有: 1.gboolean (*prepare) (GSource *source,...

q123456789098的专栏 2542

sofsip学习系列--GLib学习笔记二,事件循环2

 回到前一天的问题,除了交互性界面程序,还有哪些地方适合使用glib的event loop呢?我认为答案应该是,所有需要异步操作的地方都可以用event loop。像文件、管道、设备、socket、timer、idle其他自定义的事件都可以产生event.要让GMainLoop能够处理这些类型的event,首先就必须把它们加到GMainLoop去。首先我们需要了解event loop的这三个

flyingfalcon的专栏 1623

Glib之主事件循环

image.png 最近看开源代码一直碰到使用glib的问题,调查下使用原理。 main loop GLibGTK+应用的主事件循环管理着所有事件源。 GLib 实现了一个功能强大的事件循环分发处理机制,这些事件的来源有很多种比如文件描述符(文件、管道或套接字)或超时。 新类型的事件源可以通过g_source_attach()函数添加。 为了让多组独立事件源能够在不同的线程中被处理,每...

u012739527的博客 720

Glib编程初步

1.学习GLib编程,首先了解其运行机制--基于事件(资源)循环分发处理机制,然后理解其三大数据结构: GSource 代表资源 ;GMainContext 代表资源容器 ;GMainLoop 代表资源容器管理者,最后其工作过程:资源管理器进行循环,查询资源容器有无准备好资源,如有准备好资源,处理相应资源,否则等待。下面看一例子: 2.GLib编程实例 #include #incl

585

glib-The Main Event Loop

参考: https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GMainContext Description:The main event loop manages all the available sources of events for GLib and GTK+ applications. Thes

you 646
上一篇: meego开发的学习路线
下一篇: GMainLoop中的GSource
jack0106
博客等级 码龄18年 76粉丝 32原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值