Glib之主事件循环
介绍
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之主事件循环的更多相关文章
- pyglet--EventLoop对象(主事件循环,用于从系统消息队列中取出消息,并派发给各个窗口)
一.识别系统消息,并派出该消息 EventLoop(应用程序的事件循环),用于循环的从系统消息队列中获取系统消息(包含消息的各种参数:如鼠标位置,事件类型,鼠标左右键,哪个键盘键等),然后派发相应的事 ...
- Qt事件循环与状态机事件循环的思考
写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习.首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供.主线程有个事件循环 ...
- Qt ------ 主事件循环与 QEventLoop
1.事件循环一般用exec()函数开启.QApplicaion::exec().QMessageBox::exec()都是事件循环.其中前者又被称为主事件循环. 事件循环首先是一个无限“循环”,程序在 ...
- Qt窗口退出与事件循环退出的问题
我在Qt主程序中开启一个线程,线程中使用信号-槽来产生QMainWindow(GUI),main函数代码如下:int main(int argc, char *argv[]){ QApplicatio ...
- JavaScript单线程和浏览器事件循环简述
JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...
- Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
- JavaScript:彻底理解同步、异步和事件循环(Event Loop) (转)
原文出处:https://segmentfault.com/a/1190000004322358 一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS ...
- JS 的线程、事件循环、任务队列简介
JS 是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue). 事件循环:JS 会创建一个类似于 while (true) 的循 ...
- Node.js 学习(五)Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
随机推荐
- 把ASM下的HDD VM转换成ARM下Managed Disk的SSD VM
在ASM下,要把HDD的VM转换成SSD的VM步骤非常复杂.需要手工把Disk从普通存储账户复制到高级存储账户.再通过这个Disk创建VM. 目前在有了ASM到ARM的迁移工具,以及Managed D ...
- 在Azure上通过Powershell创建多Interface的Cisco CSR路由器
前面通过Json的Template在Azure上创建了Cisco的CSR路由器.但那个Json的template只支持1块网卡.如果需要多网卡的Cisco CSR路由器,可以改上篇文章中提到的Json ...
- C# IL中间代码注入实现切面编程
背景及现状:之前分享的那篇“面向切面编程–渲染监控日志记录方案”中提供了利用RealProxy作为代理类来生成代理的面向切面的编程方法,那个方法可以实现面向切面编程进行日志记录,现在渲染主程序也是采用 ...
- c++ 插入排序算法
第一.算法描述 直插排序很容易理解,在我们打扑克牌的时候,每一次摸完牌,都会按数字大小或者花色,插入到合适的位置,直到摸完最后一张牌,我们手中的牌已经按大小顺序排列好了.这整个过程就是一个 ...
- BIOS简单设置 解析“集成显卡”内存占用问题
很多使用集成显卡的用户会发现,在系统信息窗口中,内存容量和实际不一样.比如系统内存显示4GB,可用3.48G之类.这不可用的一部分内存到哪去了? 其实减少的这部分内存是被集成显卡占用当做显存使用了.而 ...
- angular使用代理解决跨域
angular2.angular4.angular5 及以上版本的跨域问题. 通过angular自身的代理转发功能 配置package.json 两种方式启动代理服务 第一种: 启动项目通过npm s ...
- IIS安装与部署,站点的部署与配置
第一章:IIS安装与部署 一,服务器概念的理解: 将设计好的软件只要部署到一台机器(服务器--->IIS)上,其它的员工通过浏览器(网址.)来进行访问. 做好的网站必须部署到这台机器上的IIS中 ...
- oracle——存储过程分页
1.包头: CREATE OR REPLACE PACKAGE BAWQ_PROC_PAGE IS -- BAWQ_PROC_PAGE 是包头名 TYPE T_CURSOR IS REF CURSOR ...
- 阿里云服务器访问github慢临时解决方法
su root vi /etc/hosts # github 204.232.175.78 http://documentcloud.github.com 207.97.227.239 http:// ...
- 分步编译一个C语言文件
一. 以下是C程序一般的编译过程: 从图中看到: 将编写的一个c程序(源代码 )转换成可以在硬件上运行的程序(可执行代码 ),需要进行编译阶段 和链接这两个阶段. 其中, 1. 编译阶段先通过“编 ...