跨线程处理NetworkManager的GMainContext时遇GLib断言错误求助
问题:多线程使用libnm时GLib关键警告处理
我尝试在一个线程调用NetworkManager接口,另一个线程负责迭代上下文并处理异步回调,但始终出现三个GLib关键警告:
GLib-CRITICAL **: 15:55:38.490: g_main_context_push_thread_default: assertion 'acquired_context' failed
GLib-CRITICAL **: 15:55:38.490: g_main_context_pop_thread_default: assertion 'g_queue_peek_head (stack) == context' failed
libnm-CRITICAL **: 15:55:38.491: ((libnm/nm-client.c:5925)): assertion '
' failed
最小复现代码:
#include <thread> #include <glib.h> #include <gio/gio.h> #include <iostream> #include <NetworkManager.h> class NetworkManager { public: NetworkManager() { std::cout << "Network manager creation" << std::endl; GError* error; m_client = nm_client_new(nullptr, &error); m_context = nm_client_get_main_context(m_client); m_thread = std::thread([this]() { g_main_context_push_thread_default(m_context); while (true) { g_main_context_iteration(m_context, true); } g_main_context_pop_thread_default(m_context); }); std::this_thread::sleep_for(std::chrono::seconds(1)); nm_client_reload_connections_async(m_client, nullptr, (GAsyncReadyCallback)callback, this); } ~NetworkManager() {}; static void callback(GObject*, GAsyncResult*, gpointer data) { auto nm_ptr = static_cast<NetworkManager*>(data); bool success = nm_client_reload_connections_finish(nm_ptr->m_client, nullptr, nullptr); std::cout << success << std::endl; } std::thread m_thread; GMainContext* m_context; NMClient* m_client; }; int main() { NetworkManager nm{}; int i = 0; while(true) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << i++ << std::endl; } }
我是否误解了main context的线程推入和弹出机制?已阅读Main Context教程,但仍未完全理解如何在另一个线程中正确处理main context。
原因分析与修复方案
错误根源
- 上下文所有权未获取:
nm_client_get_main_context返回的上下文默认属于创建NMClient的主线程,直接在子线程调用g_main_context_push_thread_default会触发acquired_context断言失败——GLib要求必须先获取上下文的独占访问权,才能将其设为线程默认上下文。 - 清理代码无法执行:子线程中的
g_main_context_pop_thread_default被死循环while(true)阻塞,永远不会执行,导致上下文栈状态不一致,触发第二个断言。 - libnm断言失败:异步操作的回调需要在归属正确的上下文线程执行,上下文归属错误会引发libnm内部断言失败。
修复后的代码
#include <thread> #include <glib.h> #include <gio/gio.h> #include <iostream> #include <NetworkManager.h> #include <atomic> class NetworkManager { public: NetworkManager() : m_running(true) { std::cout << "Network manager creation" << std::endl; GError* error = nullptr; m_client = nm_client_new(nullptr, &error); if (!m_client) { std::cerr << "Failed to create NMClient: " << error->message << std::endl; g_error_free(error); return; } m_context = nm_client_get_main_context(m_client); m_thread = std::thread([this]() { // 先获取上下文独占访问权,这是push线程默认的前提 if (!g_main_context_acquire(m_context)) { std::cerr << "Failed to acquire main context" << std::endl; return; } // 将上下文设为当前线程的默认上下文 g_main_context_push_thread_default(m_context); // 使用GMainLoop替代手动迭代,更符合GLib规范且易控制退出 m_loop = g_main_loop_new(m_context, FALSE); g_main_loop_run(m_loop); // 退出循环后依次清理资源 g_main_loop_unref(m_loop); g_main_context_pop_thread_default(m_context); g_main_context_release(m_context); }); // 等待子线程完成上下文初始化 std::this_thread::sleep_for(std::chrono::seconds(1)); nm_client_reload_connections_async(m_client, nullptr, (GAsyncReadyCallback)callback, this); } ~NetworkManager() { if (m_loop) { g_main_loop_quit(m_loop); } if (m_thread.joinable()) { m_thread.join(); } if (m_client) { g_object_unref(m_client); } }; static void callback(GObject*, GAsyncResult* res, gpointer data) { auto nm_ptr = static_cast<NetworkManager*>(data); GError* error = nullptr; bool success = nm_client_reload_connections_finish(nm_ptr->m_client, res, &error); if (error) { std::cerr << "Reload failed: " << error->message << std::endl; g_error_free(error); } else { std::cout << "Reload success: " << success << std::endl; } } private: std::thread m_thread; GMainContext* m_context; GMainLoop* m_loop; NMClient* m_client; std::atomic<bool> m_running; }; int main() { NetworkManager nm{}; int i = 0; while(true) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << i++ << std::endl; } }
关键说明
g_main_context_acquire:必须先调用此函数获取上下文的独占访问权,否则无法将其设置为线程默认上下文,这是解决第一个断言的核心。- 使用
GMainLoop:手动死循环迭代上下文难以正确退出,g_main_loop_run会自动处理事件循环,退出时只需调用g_main_loop_quit,保证后续清理代码(pop、release)能正常执行。 - 资源清理:析构函数中停止主循环、等待线程结束、释放
NMClient,避免资源泄漏和线程残留。 - 完善错误处理:添加
GError的检查与释放,便于排查操作失败原因。
内容的提问来源于stack exchange,提问作者joulsen
相关产品推荐
相关产品推荐

