介绍

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函数的一个变体的入参,它在事件源被释放时调用。

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

代码

数据结构

  1. struct _GSourcePrivate
  2. {
  3. GSList *child_sources;
  4. GSource *parent_source;
  5. gint64 ready_time;
  6. /* This is currently only used on UNIX, but we always declare it (and
  7. * let it remain empty on Windows) to avoid #ifdef all over the place.
  8. */
  9. GSList *fds;
  10. };
  11. struct _GSource
  12. {
  13. /*< private >*/
  14. gpointer callback_data;
  15. GSourceCallbackFuncs *callback_funcs;
  16. const GSourceFuncs *source_funcs;
  17. guint ref_count;
  18. GMainContext *context;
  19. gint priority;
  20. guint flags;
  21. guint source_id;
  22. GSList *poll_fds;
  23. GSource *prev;
  24. GSource *next;
  25. char *name;
  26. GSourcePrivate *priv;
  27. };
  28. struct _GPollRec
  29. {
  30. GPollFD *fd;
  31. GPollRec *prev;
  32. GPollRec *next;
  33. gint priority;
  34. };
  35. struct _GMainContext
  36. {
  37. /* The following lock is used for both the list of sources
  38. * and the list of poll records
  39. */
  40. GMutex mutex;
  41. GCond cond;
  42. GThread *owner;
  43. guint owner_count;
  44. GSList *waiters;
  45. gint ref_count;
  46. GHashTable *sources; /* guint -> GSource */
  47. GPtrArray *pending_dispatches;
  48. gint timeout; /* Timeout for current iteration */
  49. guint next_id;
  50. GList *source_lists;
  51. gint in_check_or_prepare;
  52. GPollRec *poll_records;
  53. guint n_poll_records;
  54. GPollFD *cached_poll_array;
  55. guint cached_poll_array_size;
  56. GWakeup *wakeup;
  57. GPollFD wake_up_rec;
  58. /* Flag indicating whether the set of fd's changed during a poll */
  59. gboolean poll_changed;
  60. GPollFunc poll_func;
  61. gint64 time;
  62. gboolean time_is_fresh;
  63. };
  64. struct _GMainLoop
  65. {
  66. GMainContext *context;
  67. gboolean is_running;
  68. gint ref_count;
  69. };
函数 说明
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事件源

  1. #define G_PRIORITY_DEFAULT_IDLE 200
  2. GSourceFuncs g_idle_funcs =
  3. {
  4. g_idle_prepare,
  5. g_idle_check,
  6. g_idle_dispatch,
  7. NULL
  8. };
  9. /* Idle functions */
  10. static gboolean g_idle_prepare(GSource *source, gint *timeout)
  11. {
  12. *timeout = 0;
  13. return TRUE;
  14. }
  15. static gboolean g_idle_check(GSource *source)
  16. {
  17. return TRUE;
  18. }
  19. static gboolean g_idle_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
  20. {
  21. gboolean again;
  22. if (!callback)
  23. {
  24. g_warning ("Idle source dispatched without callback\n"
  25. "You must call g_source_set_callback().");
  26. return FALSE;
  27. }
  28. again = callback (user_data);
  29. TRACE (GLIB_IDLE_DISPATCH (source, source->context, callback, user_data, again));
  30. return again;
  31. }
函数 说明
g_idle_add 创建一个idle事件源,然后添加到GMainContext

timeout事件源

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

GIOChannel事件源

  1. struct _GIOChannel
  2. {
  3. /*< private >*/
  4. gint ref_count;
  5. GIOFuncs *funcs;
  6. gchar *encoding;
  7. GIConv read_cd;
  8. GIConv write_cd;
  9. gchar *line_term; /* String which indicates the end of a line of text */
  10. guint line_term_len; /* So we can have null in the line term */
  11. gsize buf_size;
  12. GString *read_buf; /* Raw data from the channel */
  13. GString *encoded_read_buf; /* Channel data converted to UTF-8 */
  14. GString *write_buf; /* Data ready to be written to the file */
  15. gchar partial_write_buf[6]; /* UTF-8 partial characters, null terminated */
  16. /* Group the flags together, immediately after partial_write_buf, to save memory */
  17. guint use_buffer : 1; /* The encoding uses the buffers */
  18. guint do_encode : 1; /* The encoding uses the GIConv coverters */
  19. guint close_on_unref : 1; /* Close the channel on final unref */
  20. guint is_readable : 1; /* Cached GIOFlag */
  21. guint is_writeable : 1; /* ditto */
  22. guint is_seekable : 1; /* ditto */
  23. gpointer reserved1;
  24. gpointer reserved2;
  25. };
  26. struct _GIOFuncs
  27. {
  28. GIOStatus (*io_read)(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read, GError **err);
  29. GIOStatus (*io_write)(GIOChannel *channel, const gchar *buf, gsize count, gsize *bytes_written, GError **err);
  30. GIOStatus (*io_seek)(GIOChannel *channel, gint64 offset, GSeekType type, GError **err);
  31. GIOStatus (*io_close)(GIOChannel *channel, GError **err);
  32. GSource* (*io_create_watch)(GIOChannel *channel, GIOCondition condition);
  33. void (*io_free)(GIOChannel *channel);
  34. GIOStatus (*io_set_flags)(GIOChannel *channel, GIOFlags flags, GError **err);
  35. GIOFlags (*io_get_flags)(GIOChannel *channel);
  36. };
  37. GSourceFuncs g_io_watch_funcs = {
  38. g_io_unix_prepare,
  39. g_io_unix_check,
  40. g_io_unix_dispatch,
  41. g_io_unix_finalize
  42. };
  43. static GIOFuncs unix_channel_funcs = {
  44. g_io_unix_read,
  45. g_io_unix_write,
  46. g_io_unix_seek,
  47. g_io_unix_close,
  48. g_io_unix_create_watch,
  49. g_io_unix_free,
  50. g_io_unix_set_flags,
  51. g_io_unix_get_flags,
  52. };
函数 说明
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. 3.Monkey Script小案例

    1.实现打开搜狗搜索APP,在搜索框输入内容,点击回车,重复2次运行 2.实现代码如下所示: type=user count=10 speed=1.0 start data >> Laun ...

  2. laravel config文件的使用

    好多东西 由于许多地方都要使用与将来可能发生更改 我们需要把它提取出来 作为配置文件来使用 这样将来要修改的时候 只需要修改一处即可 学习源头: https://blog.csdn.net/linyu ...

  3. POJ2230(打印欧拉回路)

    Watchcow Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 7473   Accepted: 3270   Specia ...

  4. [转载]Python print函数用法,print 格式化输出

    使用print输出各型的 字符串 整数 浮点数 出度及精度控制 strHello = 'Hello Python' print strHello #输出结果:Hello Python #直接出字符串 ...

  5. maven如何过滤占位符

    今天遇到一个问题,就是properties文件中赋值用的这种形式${xxx},真正的值是配置在pom的profile中,但是未生效. 后来找到原因,原来是pom中少了一段代码: <build&g ...

  6. leetcode874

    这道题直接按照题意来解,建立坐标系和移动方案,思路是比较简单的.只是需要注意需要使用set来判断是否遇到障碍,否则会超时. int robotSim(vector<int>& co ...

  7. 清空select标签中option选项的4种不同方式

    转自:https://blog.csdn.net/pt_sm/article/details/53521560 方法一 document.getElementById("selectid&q ...

  8. awk简要使用

    1          前言 awk是Unix环境下一种非常好的语言,适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行特殊技巧程序设计.对于短消息来说,比如处理话单文件,使用awk就非常方便 ...

  9. Eclipse Maven 编译错误 Dynamic Web Module 3.0 requires Java 1.6 or newer 解决方案

    Eclipse Maven 开发一个 jee 项目时,编译时遇到以下错误:Description Resource Path Location TypeDynamic Web Module 3.0 r ...

  10. plupload的一些使用心得

    最近要做一个文件上传的东西 经过同事的推荐所以就选择了plupload,挺强大的 由于项目框架为改动后的MVC 刚一开始破费周折 不过最后总算是完成了 废话不多说了 粘出来代码给大家参考吧!文件包大家 ...