glib g_main_loop理解

本文介绍了Glib Mainloop的工作原理及其实现机制,包括mainloop的使用模式、关键组件及事件源接口等,并详细探讨了几种内置事件源的实现。

 http://soft.zdnet.com.cn/software_zone/2008/0214/735181.shtml

 

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就行了。

 

 

<think>我们被要求提供关于`g_main_loop_new`的使用方法和示例。根据引用[1]中提供的信息,我们知道`GMainLoop`是GLib库中主事件循环的数据结构,通过`g_main_loop_new`创建。主事件循环用于处理多个事件源的事件,直到显式退出。 ### 函数原型 ```c GMainLoop * g_main_loop_new (GMainContext *context, gboolean is_running); ``` ### 参数说明 1. **`context`**: 一个`GMainContext`指针,表示事件循环的上下文。如果传入`NULL`,则使用默认的上下文。 2. **`is_running`**: 一个布尔值,表示创建后是否立即运行循环。通常设置为`FALSE`,然后通过`g_main_loop_run()`启动。 ### 返回值 返回一个指向`GMainLoop`的指针。 ### 基本使用步骤 1. **创建主循环对象**: 使用`g_main_loop_new()`创建。 2. **添加事件源**: 例如超时、IO监视器、空闲函数等(这里不展开,但示例中会包含一个超时事件)。 3. **运行主循环**: 调用`g_main_loop_run()`,该函数会阻塞直到调用`g_main_loop_quit()`退出循环。 4. **退出循环**: 在某个事件处理中调用`g_main_loop_quit()`。 5. **释放资源**: 使用`g_main_loop_unref()`减少引用计数,当引用计数为0时释放资源。 ### 示例代码 以下是一个简单的C语言示例,创建一个主循环,并添加一个超时事件,5秒后退出主循环。 ```c #include <glib.h> // 超时回调函数,返回TRUE表示持续调用,FALSE表示移除 static gboolean timeout_callback(gpointer data) { static int count = 0; g_print("超时回调被调用,count = %d\n", count++); // 5次后退出主循环 if (count >= 5) { g_main_loop_quit((GMainLoop *)data); return FALSE; // 移除超时源 } return TRUE; // 继续调用 } int main() { // 创建主循环,使用默认上下文 GMainLoop *loop = g_main_loop_new(NULL, FALSE); // 添加一个超时事件,每1秒触发一次 guint timeout_id = g_timeout_add_seconds(1, timeout_callback, loop); g_print("开始运行主事件循环...\n"); g_main_loop_run(loop); // 阻塞在这里,直到调用g_main_loop_quit g_print("主事件循环已退出。\n"); // 清理 g_source_remove(timeout_id); // 移除超时源(其实在回调中已经返回FALSE移除了,这里为了安全) g_main_loop_unref(loop); // 释放主循环 return 0; } ``` ### 代码说明 1. **创建主循环**:`g_main_loop_new(NULL, FALSE)`创建了一个使用默认上下文的主循环,创建后不立即运行。 2. **添加超时事件**:`g_timeout_add_seconds(1, timeout_callback, loop)`添加一个每秒触发一次的超时事件,回调函数为`timeout_callback`,并将主循环对象作为用户数据传入。 3. **运行主循环**:`g_main_loop_run(loop)`开始事件循环,此时程序会阻塞在这里,直到调用`g_main_loop_quit(loop)`。 4. **回调函数**:在回调函数中,当被调用5次后,调用`g_main_loop_quit(loop)`退出循环,并返回`FALSE`表示不再需要该超时事件(移除事件源)。 5. **清理**:退出循环后,我们移除超时源(虽然回调返回FALSE已经自动移除,但显式移除是良好实践)并释放主循环对象。 ### 编译 在Linux环境下,使用以下命令编译(需要安装glib库): ```bash gcc main.c -o main `pkg-config --cflags --libs glib-2.0` ``` ### 注意事项 - 主循环是单线程的,所有的事件处理都在同一个线程中按顺序执行。 - 如果需要处理多线程,每个线程可以有自己独立的`GMainContext`和`GMainLoop`。 - 确保在退出循环后释放相关资源,避免内存泄漏。 ### 引用说明 以上内容参考了引用[1]中关于`GMainLoop`的基本描述,并提供了实际使用示例。 --- §§相关问题§§ 1. 如何在GLib主循环中处理多个事件源? 2. `GMainContext`和`GMainLoop`之间的关系是什么? 3. 在GLib中如何实现异步IO操作?</think># `g_main_loop_new` 函数详解与使用指南 `g_main_loop_new` 是 GLib 库中用于创建**主事件循环**的核心函数,它构成了 GTK 应用程序和 GLib 异步操作的基础架构。主事件循环负责处理应用程序的所有事件、信号和异步操作[^1]。 ## 函数原型与参数 ```c GMainLoop* g_main_loop_new( GMainContext *context, // 事件循环上下文 gboolean is_running // 是否立即运行 ); ``` ### 参数说明 1. **`context`**: - 事件循环的上下文对象 - 传入 `NULL` 表示使用默认上下文 - 多个事件循环可以共享同一个上下文 2. **`is_running`**: - 布尔值,控制是否在创建后立即运行 - `TRUE`:立即运行 - `FALSE`:需手动调用 `g_main_loop_run()` 启动 ### 返回值 - 成功时返回 `GMainLoop*` 指针 - 失败时返回 `NULL` ## 核心概念解析 ### 1. 主事件循环组件 ```mermaid graph LR A[GMainLoop] --> B[事件源] A --> C[事件分发] A --> D[信号处理] A --> E[异步操作] ``` ### 2. 事件处理流程 ```mermaid sequenceDiagram participant App as 应用程序 participant Loop as 主事件循环 participant Source as 事件源 App->>Loop: g_main_loop_new() App->>Loop: g_main_loop_run() loop 事件处理 Loop->>Source: 检查事件 Source-->>Loop: 新事件 Loop->>App: 触发回调 end App->>Loop: g_main_loop_quit() ``` ## 完整使用示例 ### 基础示例:创建并运行主循环 ```c #include <glib.h> // 超时回调函数 static gboolean timeout_callback(gpointer user_data) { static int count = 0; g_print("超时事件触发: %d\n", count++); // 5次后退出主循环 if (count >= 5) { g_main_loop_quit((GMainLoop*)user_data); return FALSE; // 停止此定时器 } return TRUE; // 继续定时器 } int main() { // 创建主循环(不立即运行) GMainLoop *loop = g_main_loop_new(NULL, FALSE); // 添加每秒触发的超时事件 g_timeout_add_seconds(1, timeout_callback, loop); g_print("启动主事件循环...\n"); g_main_loop_run(loop); // 阻塞直到调用quit g_print("主循环已退出\n"); // 释放资源 g_main_loop_unref(loop); return 0; } ``` ### 编译命令 ```bash gcc main.c -o main `pkg-config --cflags --libs glib-2.0` ``` ### 输出结果 ``` 启动主事件循环... 超时事件触发: 0 超时事件触发: 1 超时事件触发: 2 超时事件触发: 3 超时事件触发: 4 主循环已退出 ``` ## 高级用法 ### 1. 多线程事件循环 ```c // 工作线程函数 static gpointer worker_thread(gpointer data) { GMainLoop *loop = g_main_loop_new(NULL, FALSE); // 添加线程特定事件源... g_main_loop_run(loop); g_main_loop_unref(loop); return NULL; } int main() { // 创建主循环 GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); // 创建工作线程 GThread *thread = g_thread_new("worker", worker_thread, NULL); // 运行主循环 g_main_loop_run(main_loop); // 清理 g_thread_join(thread); g_main_loop_unref(main_loop); } ``` ### 2. 自定义上下文 ```c // 创建独立上下文 GMainContext *context = g_main_context_new(); // 创建使用此上下文的主循环 GMainLoop *loop = g_main_loop_new(context, FALSE); // 在另一个线程处理事件 g_main_context_push_thread_default(context); // ... 添加事件源 ... // 运行循环 g_main_loop_run(loop); // 清理 g_main_loop_unref(loop); g_main_context_unref(context); ``` ## 关键API说明 1. **`g_main_loop_run(GMainLoop *loop)`**: - 启动事件循环 - 阻塞当前线程直到调用 `quit` 2. **`g_main_loop_quit(GMainLoop *loop)`**: - 退出事件循环 - 可在任何线程安全调用 3. **`g_main_loop_is_running(GMainLoop *loop)`**: - 检查循环是否正在运行 4. **`g_main_loop_ref/unref(GMainLoop *loop)`**: - 引用计数管理 ## 最佳实践 1. **资源管理**: ```c GMainLoop *loop = g_main_loop_new(NULL, FALSE); // 使用循环... g_main_loop_unref(loop); // 必须释放 ``` 2. **安全退出**: ```c // 在信号处理中安全退出 static void handle_signal(int sig) { g_main_loop_quit(global_loop); } ``` 3. **上下文选择**: - 单线程应用:使用默认上下文 (`NULL`) - 多线程应用:为每个线程创建独立上下文 4. **异步操作集成**: ```c // Python示例(参考引用[3]) async def run_command(): loop = asyncio._get_running_loop() # 在GLib事件循环中执行系统命令 await invoke_command_async("ping", loop=loop) ``` ## 常见问题解决 **Q: 主循环不退出怎么办?** A: 确保所有事件源都被正确移除,特别是返回 `TRUE` 的定时器。 **Q: 如何调试事件循环?** A: 设置环境变量 `G_MESSAGES_DEBUG=all` 查看详细事件日志。 **Q: 多线程下如何安全操作?** A: 使用 `g_main_context_invoke()` 跨线程安全调用函数。 ---
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值