问题 使用GTK解决问题


我正在构建一个相当简单的 C申请 使用GTK,但必须执行一些阻止IO,这将触发GUI的更新。为了做到这一点,我开始新的 pthread 就在之前 gtk_main() 因此:

/* global variables */
GMainContext *mainc;

/* local variables */
FILE *fifo;
pthread_t reader;

/* main() */
mainc = g_main_context_default();
pthread_create(&reader, NULL, watch_fifo, argv[argc-1]);
gtk_main();

当。。。的时候 pthread 读取一些数据,它更新GUI如下:

g_main_context_invoke(mainc, set_icon, param);

哪里 set_icon 是

gboolean set_icon(gpointer data)
{
    char *p = (char*)data;
    gtk_status_icon_set_from_icon_name(icon, p);
    return FALSE;
}

这一切都在大部分时间都有效,但我不时地得到这个奇怪的错误信息:

[xcb]处理队列时的序列号未知
[xcb]很可能这是一个多线程客户端,而且还没有调用XInitThreads
[xcb]中止,对不起。
mktrayicon:xcb_io.c:274:poll_for_event:断言`!xcb_xlib_threads_sequence_lost'失败。

我想到了使用的全部意义 g_main_context_invoke 是为了避免线程问题?做了一些谷歌搜索,我遇到了 gdk_threads_initgdk_threads_enter 和朋友们,但他们似乎都被弃用了?我知道GTK文档说所有的GUI更新都应该在主线程上执行,但这并不能很好地结合阻塞IO,我宁愿不必在线程之间构建一些复杂的通信机制。

所以,我的问题是,我该如何正确处理这个?

编辑:可以看到完整的代码 这里 EDIT2:作为基于@ ptomato答案的更新,我已经搬到了 GThread和使用 gdk_threads_add_idle() 正如所见 这个 提交,但问题仍然存在。


8362
2017-09-05 23:22


起源

你有一些代码可用,我已经使用gtk +很长一段时间了 决不 偶然发现了这个问题.. - drahnr
整个代码可在 GitHub上 与帖子中的链接相关联。 - Jon Gjengset
啊,错过了谢谢! - drahnr
请注意,您将要使用 这个 链接查看原始代码,和 这个 链接以查看@ptomato实施建议后的代码。 这个 链接将指向您的文件的固定版本。 - Jon Gjengset


答案:


呼叫 XInitThreads()。这应该在此之前完成 gtk_init,这将阻止消息!

像这样的东西:

    #include <X11/Xlib.h>
    ...  
    XInitThreads();
    ...
    gtk_init(&argc, &argv);

我不记得在GLIB 2.32之前看过这些消息了 g_thread_init()/gdk_threads_init() 被使用了。

你可能想看看 g_thread_pool_new 和 g_thread_pool_push。 从线程,使用 g_main_context_invoke 在主循环或执行中执行 只是在之间包装线程 gdk_threads_enter()/gdk_threads_leave()

我不使用托盘所以我不能轻易检查这个。我想你是 使用锁来纠正gdk_threads_add_idle以保护GTK / GDK API。 对我来说没有什么可以导致这些消息 出现。 gtk_status_icon_new_from_icon_name的函数说明 声明“如果当前图标主题已更改,则图标将为 适当更新。对我而言,暗示您的代码不是唯一的 将访问X显示的代码,可能是 问题。

还有一些关于XInitThreads()的相关信息

XInitThreads()的缺点是什么?

请注意,虽然GDK使用锁定显示,但GTK / GDK不会 调用XInitThreads。

在旁注:什么保护全局变量“onclick”,哪个 在fork()之后传递给execl,子节点不会继承父节点 内存锁和GLib主循环与fork()不兼容。 也许你可以将字符串复制到局部变量。


10
2017-09-09 01:55



gdk_threads_enter 和 gdk_threads_leave 已被弃用,因此使用它们并非真正的选择。考虑到我只有一个额外的线程,使用线程池似乎有点矫枉过正,但感谢提示!似乎很奇怪,不得不打电话 XInitThreads 手动考虑手册说“GLib线程系统会在程序开始时自动初始化”,但我今天晚些时候会给它一个报告并报告回来。 - Jon Gjengset
还有,打电话不是有点奇怪 XInitThreads 什么时候的文件 gdk_threads_add_idle_full 说它“打电话 function 随着GDK锁举行“,和 XInitThreads docs说“如果对Xlib函数的所有调用都受到其他访问机制的保护(例如,工具包中的互斥锁或通过显式客户端编程),则不需要Xlib线程初始化”? - Jon Gjengset
这个 修好了问题,谢谢!还是会有更多的信息 为什么 这是为赏金而发生的。 - Jon Gjengset
阅读完答案后的链接 XInitThreads,即使它有效,我也不喜欢打电话。好像在使用 gdk_threads_add_idle,已经获得GTK锁定,应该足够......就你的旁注而言,这应该不是问题,因为内存空间被复制了 fork 然后“擦” execl。家长 onclick var在进一步向下更改时被释放。如果你正在考虑X锁,那么新的 forked过程将完全独立,并且不必担心GDK / GTK会启动它。 - Jon Gjengset
我认为这可能是GTK或GDK中的错误。如果你可以将它减少到一个最小的例子(诚然,很难处理线程错误),肯定会在bugzilla.gnome.org上报告。 - ptomato


我不确定裸pthreads是否可以保证与GTK一起使用。你应该使用GThread包装器。

我想问题可能是什么 g_main_context_invoke() 正在加入 set_icon()作为空闲功能。 (看来这就是幕后的情况,但我不确定。)使用GLib API添加的空闲函数,尽管在主线程上执行,但需要保持GDK锁定。如果你使用 gdk_threads_add_idle() 要调用的API(不推荐使用) set_icon(),然后一切都应该与线程正常工作。

(虽然这只是一个疯狂的猜测。)


1
2017-09-06 06:08



我已经改为GThreads了 gdk_threads_add_idle 正如所见 在这个提交中,但我偶尔会看到错误(或其微小变化)...... - Jon Gjengset
是的,GTK和PThreada是兼容的。 - Lothar


作为一种解决方法,如果您只是想在等待某些IO时避免阻止UI,则可以使用异步IO GIO。这样可以避免您自己管理线程。

编辑:考虑一下你可以将你的文件描述符标记为非阻塞,并将它们作为源添加到glib主循环中,它将在主事件循环中为你轮询它们而不必乱用线程。


0
2017-09-09 09:13



轮询它似乎不是一个特别干净的解决方案。我宁愿阻止它,因为这意味着程序会立即对命令作出反应。 GIO似乎很有趣。你能举例说明我如何使用它吗? - Jon Gjengset
轮询不应该比阻塞读取慢,这是GTK用来对鼠标和键盘事件做出反应的。要做到这一点,创建一个 GIOChannel 同 g_io_channel_new_file 并将其添加到主循环中 g_io_add_watch。为GIO创建一个 GFile 同 g_file_new_for_commandline_arg 要么 g_file_new_for_path 然后打电话 g_file_read_async 带有回调函数 g_file_read_finish 其次是 g_input_stream_read_async 带有回调函数 g_input_stream_read_finish 然后处理读取的数据然后调用 g_input_stream_read_async 再次。 - Phillip Wood


您可以通过使用gio_add_watch()避免使用线程,当通道上有可用数据时,它会调用您的回调函数。


0
2017-09-10 06:56



使用FIFO文件并不完全是微不足道的,因为我使用它们的方式,每次遇到EOF时都需要重新打开(阻塞),因为编写器来来往往。 g_io_add_watch 似乎不支持这个?如我错了请纠正我。 - Jon Gjengset