介绍

GLib和GTK+应用的主事件循环管理着所有事件源。这些事件的来源有很多种比如文件描述符(文件、管道或套接字)或超时。新类型的事件源可以通过g_source_attach()函数添加。

为了让多组独立事件源能够在不同的线程中被处理,每个事件源都会关联一个GMainContext。一个线程只能运行一个GMainContext,但是在其他线程中能够对事件源进行添加和删除操作。

每个事件源都被赋予了优先级。默认的优先级是G_PRIORITY_DEFAULT(0)。值越小优先级越高,优先级高的事件源优先处理。

Idle函数在没有更高优先级的事件被处理的时候才会执行。

GMainLoop数据类型代表了一个主事件循环。通过g_main_loop_new()来创建GMainLoop对象。在添加完初始事件源后执行g_main_loop_run(),主循环将持续不断的检查每个事件源产生的新事件,然后分发它们,直到处理来自某个事件源的事件的时候触发了g_main_loop_quit()调用退出主循环为止。

GMainLoop实例能够被递归创建。在GTK+应用中经常使用这种方式来显示模态对话框。注意如果一个事件源被添加到一个GMainContext,那么它将被所有关联这个GMainContext的主线程检查和分发。

GTK+对这些函数做了些封装,例如gtk_main、gtk_mian_quit和gtk_events_pending。

自定义事件类型

GMainLoop一个不常用的特性就是能够创建一个新的事件源类型,然后当做内置事件源的扩展来使用。一个新的事件源类型通常用来处理GDK事件。通过继承GSource结构来创建一个新的事件源类型。继承产生的新事件源类型表示GSource结构作为新事件源类型的第一个元素然后其他元素紧跟其后。使用g_source_new函数来创建新的事件源类型实例,函数的参数就是新的事件源类型大小。GSourceFuncs决定新的事件源类型的行为。

新的事件源有两种基本方式与GMainContext交互。它们GSourceFuncs中的准备函数能够设置睡眠事件,用来决定主循环下次检测它们的时间。此外事件源也可以使用g_source_add_poll()函数添加文件描述符到GMainContext进行检测。

自定义主循环迭代

执行g_main_context_iteration()函数可以完成GMainContext的单次迭代。在一些情况下,我们可能想获取主循环更多的底层控制细节,我们可以调用g_main_context_iteration()里的组件函数:g_main_context_prepare()g_main_context_prepare ()g_main_context_query()g_main_context_check()g_main_context_dispatch()

Main Context状态

在UNIX系统上,GLib的主循环和fork()是不兼容的。

事件源内存管理

有两种可选的方式来管理传递给GSource回调函数用户数据的内存。用户数据就是在调用g_timeout_add()g_timeout_add_full()g_idle_add()传入的参数。这些数据通常被timeout或idle回调函数所拥有,比如一个构件或一个网路协议的实现。有些时候这些回调函数会在数据被销毁的后背调用,因为使用了已经被释放的内存,所以这会导致一个错误。

  • 第一种推荐的方法就是保存g_timeout_add()g_source_attach()返回的事件源ID,然后在其维持的用户数据被释放后显示的将其从GMainContext移除。这样就能保证调用这些回调函数的时候,这些用户数据依然有效。
  • 第二种就是保存这些回调函数中的用户数据对象的引用,然后在GDestroyNotify回调函数中释放它。这样就能确保数据对象在事件源最后一次调用然后被释放前一直有效。GDestroyNotify回调函数是GSource函数的一个变体的入参,它在事件源被释放时调用。

    第二条途径有必要提醒下,如果在事件源还没被调用前主循环就结束的情况下,用户数据对象被维持状态是不确定的。

代码

数据结构

struct _GSourcePrivate
{
GSList *child_sources;
GSource *parent_source; gint64 ready_time; /* This is currently only used on UNIX, but we always declare it (and
* let it remain empty on Windows) to avoid #ifdef all over the place.
*/
GSList *fds;
}; struct _GSource
{
/*< private >*/
gpointer callback_data;
GSourceCallbackFuncs *callback_funcs; const GSourceFuncs *source_funcs;
guint ref_count; GMainContext *context; gint priority;
guint flags;
guint source_id; GSList *poll_fds; GSource *prev;
GSource *next; char *name; GSourcePrivate *priv;
}; struct _GPollRec
{
GPollFD *fd;
GPollRec *prev;
GPollRec *next;
gint priority;
}; struct _GMainContext
{
/* The following lock is used for both the list of sources
* and the list of poll records
*/
GMutex mutex;
GCond cond;
GThread *owner;
guint owner_count;
GSList *waiters; gint ref_count; GHashTable *sources; /* guint -> GSource */ GPtrArray *pending_dispatches;
gint timeout; /* Timeout for current iteration */ guint next_id;
GList *source_lists;
gint in_check_or_prepare; GPollRec *poll_records;
guint n_poll_records;
GPollFD *cached_poll_array;
guint cached_poll_array_size; GWakeup *wakeup; GPollFD wake_up_rec; /* Flag indicating whether the set of fd's changed during a poll */
gboolean poll_changed; GPollFunc poll_func; gint64 time;
gboolean time_is_fresh;
}; struct _GMainLoop
{
GMainContext *context;
gboolean is_running;
gint ref_count;
};
函数 说明
g_main_context_add_poll_unlocked 在g_source_add_poll和g_source_add_unix_fd中被调用,首先将需要监听的文件描述符保存到_GSource->poll_fds或_GSource->priv->fds中,然后再创建个_GPollRec对象,接着将其保存到_GMainContext->poll_records中,最后设置context->poll_changed为True,这个值会影响当前主循环的g_main_context_check
g_source_attach_unlocked 在g_source_attach和g_source_add_child_source中被调用,将_GSource保存到_GMainContext->sources,并将_GSource保存的文件描述符通过g_main_context_add_poll_unlocked函数保存到_GMainContext,最后返回_GSource->source_id
g_main_context_prepare 遍历_GMainContext拥有的事件源,调用事件源的_GSourceFuncs->prepare函数计算下次轮训间隔,并检测事件源是否已经就绪
g_main_context_query 从_GMainContext拥有的事件源中筛选出需要进行poll的事件源,并设置context->poll_changed为False(只要不是在poll期间对事件源进行添加或删除操作即可)
g_main_context_poll 使用poll函数监听g_main_context_query筛选出的事件源
g_main_context_check 遍历g_main_context_query筛选出的事件源,调用事件源的_GSourceFuncs->check函数检测事件源是否已经就绪,如果就绪则将事件源添加到_GMainContext->pending_dispatches中。如果context->poll_changed为True(说明在poll的时候有新的事件源加入或移除),则跳过后面的分发(pending_dispatches为空),重新执行g_main_context_iterate
g_main_context_dispatch 遍历_GMainContext->pending_dispatches中的事件源,并调用事件源的_GSourceFuncs->dispatch函数进行分发

idle事件源

#define G_PRIORITY_DEFAULT_IDLE     200

GSourceFuncs g_idle_funcs =
{
g_idle_prepare,
g_idle_check,
g_idle_dispatch,
NULL
}; /* Idle functions */ static gboolean g_idle_prepare(GSource *source, gint *timeout)
{
*timeout = 0; return TRUE;
} static gboolean g_idle_check(GSource *source)
{
return TRUE;
} static gboolean g_idle_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
gboolean again; if (!callback)
{
g_warning ("Idle source dispatched without callback\n"
"You must call g_source_set_callback().");
return FALSE;
} again = callback (user_data); TRACE (GLIB_IDLE_DISPATCH (source, source->context, callback, user_data, again)); return again;
}
函数 说明
g_idle_add 创建一个idle事件源,然后添加到GMainContext

timeout事件源

#define G_PRIORITY_DEFAULT          0

GSourceFuncs g_timeout_funcs =
{
NULL, /* prepare */
NULL, /* check */
g_timeout_dispatch,
NULL
}; static gboolean g_timeout_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
GTimeoutSource *timeout_source = (GTimeoutSource *)source;
gboolean again; if (!callback)
{
g_warning ("Timeout source dispatched without callback\n"
"You must call g_source_set_callback().");
return FALSE;
} again = callback (user_data); TRACE (GLIB_TIMEOUT_DISPATCH (source, source->context, callback, user_data, again)); if (again)
g_timeout_set_expiration (timeout_source, g_source_get_time (source)); return again;
}
函数 说明
g_timeout_add 创建一个timeout事件源,然后添加到GMainContext。timeout事件源没有自己的prepare和check函数,是因为在g_main_context_prepare和g_main_context_check 中都会获取下当前系统时间,然后和timeout事件源的对比,如果时间已经过了,则将事件源的状态修改为就绪状态,所以就不需要自己来写了

GIOChannel事件源

struct _GIOChannel
{
/*< private >*/
gint ref_count;
GIOFuncs *funcs; gchar *encoding;
GIConv read_cd;
GIConv write_cd;
gchar *line_term; /* String which indicates the end of a line of text */
guint line_term_len; /* So we can have null in the line term */ gsize buf_size;
GString *read_buf; /* Raw data from the channel */
GString *encoded_read_buf; /* Channel data converted to UTF-8 */
GString *write_buf; /* Data ready to be written to the file */
gchar partial_write_buf[6]; /* UTF-8 partial characters, null terminated */ /* Group the flags together, immediately after partial_write_buf, to save memory */ guint use_buffer : 1; /* The encoding uses the buffers */
guint do_encode : 1; /* The encoding uses the GIConv coverters */
guint close_on_unref : 1; /* Close the channel on final unref */
guint is_readable : 1; /* Cached GIOFlag */
guint is_writeable : 1; /* ditto */
guint is_seekable : 1; /* ditto */ gpointer reserved1;
gpointer reserved2;
}; struct _GIOFuncs
{
GIOStatus (*io_read)(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read, GError **err);
GIOStatus (*io_write)(GIOChannel *channel, const gchar *buf, gsize count, gsize *bytes_written, GError **err);
GIOStatus (*io_seek)(GIOChannel *channel, gint64 offset, GSeekType type, GError **err);
GIOStatus (*io_close)(GIOChannel *channel, GError **err);
GSource* (*io_create_watch)(GIOChannel *channel, GIOCondition condition);
void (*io_free)(GIOChannel *channel);
GIOStatus (*io_set_flags)(GIOChannel *channel, GIOFlags flags, GError **err);
GIOFlags (*io_get_flags)(GIOChannel *channel);
}; GSourceFuncs g_io_watch_funcs = {
g_io_unix_prepare,
g_io_unix_check,
g_io_unix_dispatch,
g_io_unix_finalize
}; static GIOFuncs unix_channel_funcs = {
g_io_unix_read,
g_io_unix_write,
g_io_unix_seek,
g_io_unix_close,
g_io_unix_create_watch,
g_io_unix_free,
g_io_unix_set_flags,
g_io_unix_get_flags,
};
函数 说明
g_io_channel_new_file 获取并保存文件句柄,然后使用unix_channel_funcs初始化_GIOChannel->funcs
g_io_create_watch 使用g_io_unix_create_watch函数将channel保存的文件句柄转换为事件源,事件源函数为g_io_watch_funcs
g_io_add_watch 使用g_io_create_watch创建事件源,然后附加到当前主循环的GMainContext
g_io_unix_prepare 获取channel的输入输出buffer的数据状态,并与需要监听的channel事件进行对比
g_io_unix_check 获取channel的输入输出buffer的数据状态,并与channel实际触发的事件进行对比

Glib之主事件循环的更多相关文章

  1. pyglet--EventLoop对象(主事件循环,用于从系统消息队列中取出消息,并派发给各个窗口)

    一.识别系统消息,并派出该消息 EventLoop(应用程序的事件循环),用于循环的从系统消息队列中获取系统消息(包含消息的各种参数:如鼠标位置,事件类型,鼠标左右键,哪个键盘键等),然后派发相应的事 ...

  2. Qt事件循环与状态机事件循环的思考

    写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习.首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供.主线程有个事件循环 ...

  3. Qt ------ 主事件循环与 QEventLoop

    1.事件循环一般用exec()函数开启.QApplicaion::exec().QMessageBox::exec()都是事件循环.其中前者又被称为主事件循环. 事件循环首先是一个无限“循环”,程序在 ...

  4. Qt窗口退出与事件循环退出的问题

    我在Qt主程序中开启一个线程,线程中使用信号-槽来产生QMainWindow(GUI),main函数代码如下:int main(int argc, char *argv[]){ QApplicatio ...

  5. JavaScript单线程和浏览器事件循环简述

    JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...

  6. Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  7. JavaScript:彻底理解同步、异步和事件循环(Event Loop) (转)

    原文出处:https://segmentfault.com/a/1190000004322358 一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS ...

  8. JS 的线程、事件循环、任务队列简介

    JS 是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue). 事件循环:JS 会创建一个类似于 while (true) 的循 ...

  9. Node.js 学习(五)Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

随机推荐

  1. Azure ARM模式下VNet配置中需要注意的几点事项

    虚拟网络的配置是所有公有云中非常重要的环节.把虚拟网络配置好,对整个系统的管理.维护,以及安全性都非常重要. 本文将介绍Azure在ARM模式下VNet配置中需要特别注意的几点. 一 Azure的VN ...

  2. 什么是VBA,他有什么作用

    目录 什么是VBA,它有什么作用 VBA在哪里存放的?怎么运行的 什么是宏?宏和VBA有什么关系? 录制一个宏 编写第一个宏 一.什么是VBA,它有什么作用 VBA是一种编程语言,它依托于Office ...

  3. Anti-pattern(反模式)

    常见的与“直觉”相背离的 anti-pattern 产生的实际原因是我们没有深入全面地考虑问题. 即只关注到自己关心的方面,忽略了其他重要的.恰好起相反作用的因素. 所以这个“直觉”是不成熟.不全面的 ...

  4. 西安电子科技大学第16届程序设计竞赛 E Xieldy And His Password

    链接:https://www.nowcoder.com/acm/contest/107/E来源:牛客网 Xieldy And His Password 时间限制:C/C++ 1秒,其他语言2秒 空间限 ...

  5. MyCAT 1.6 安装部署

    1 mycat 安装部署 1 下载解压Mycat-server-1.6-RELEASE-20161010173036-linux.tar.gz 到/usr/local/mycat 2 下载解压jdk- ...

  6. 基于OpenCV的火焰检测(一)——图像预处理

    博主最近在做一个基于OpenCV的火焰检测的项目,不仅可以检测图片中的火焰,还可以检测视频中的火焰,最后在视频检测的基础上推广到摄像头实时检测.在做这个项目的时候,博主参考了很多相关的文献,用了很多种 ...

  7. 使用pip一次升级所有安装的Python包(太牛了)

    import pip from subprocess import call for dist in pip.get_installed_distributions(): call("pip ...

  8. nginx注册成服务

    http://blog.csdn.net/t37240/article/details/51727563

  9. 部署和调优 2.5 tomcat配置和优化

    配置文件 vim /usr/local/tomcat/conf/server.xml 修改 <Connector port=" protocol="HTTP/1.1" ...

  10. xUtils 源码解析

    1. 功能介绍 xUtils 一个 Android 公共库框架,主要包括四个部分:View,Db, Http, Bitmap 四个模块. View 模块主要的功能是通过注解绑定 UI,资源,事件. D ...