在 Gtk+2.0 应用程序中使用 pthreads 的安全性
Safety of using pthreads in Gtk+2.0 application
我有一个简单的多线程 Gtk+2.0 应用程序,它从多个来源(麦克风、网络摄像头、温度传感器)获取数据,并将这些数据显示为屏幕上的图像(网络摄像头帧抓取、麦克风数据表示为示波器渲染、文本等)。
根据我对 Gtk 手册 and various articles 的理解,只有主处理线程应该使用任何影响 UI 的 Gtk functions/calls。但是,main()
入口点会阻塞 gtk_main()
,直到我关闭 UI。除了映射到诸如当我单击 UI 中的按钮或滑块时的事件处理程序之外,似乎唯一对我开放的选项是产生一些 pthread
s 并具有他们在 UI.
中定期对数据进行采样并更新屏幕上的信息
我记得很久以前做一些 MFC GUI 开发时应用了类似的原则:只有一个特定的线程应该更新 UI 元素。我如何使用 Gtk+2.0 在 C 中完成此操作?
谢谢。
我会按照您的建议在不同的线程中进行采样。接下来的问题是如何更新 UI。我会做的是使用 'self-pipe'。这通常是为了从信号处理程序进行通信而完成的,但是当其中一个线程不能等待条件变量时,它在线程之间工作得很好。这里做的是建立一个专用管道,在已经拿到数据的线程中向管道写入一个字符,在主程序的select()
循环中在管道的读端写入select()
。详情在这里:Using self-pipe, how can I avoid that the event loop stalls on read()? - useful background on its origins here.
然后您可以让 GTK+ 侦听管道的读取端。您的问题因此减少为让 GTK+ 响应 FD 上的某些内容 - 请参阅 here(第一个答案)了解如何。
根据documentation,主偶数循环可以接受来自不同线程的源:
A GMainContext can only be running in a single thread, but sources can
be added to it and removed from it from other threads.
因此您可以通过以下方式从您的工作线程将代码注入 UI 线程:
- 创建
GSource
(例如,使用 g_idle_source_new
);
- 添加要执行的代码
g-source-set-callback
;
- 使用
g_source_attach()
. 将其附加到 UI 线程上下文
这是 GTK+2 和 GLib >= 2.32 的示例程序:
#include <gtk/gtk.h>
#define N_THREADS 100
#define N_ITERATIONS 100
GtkWidget *bar;
GMainContext *context;
static gboolean
update_progress_bar(gpointer user_data)
{
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar),
g_random_double_range(0, 1));
return G_SOURCE_REMOVE;
}
static gpointer
thread_func(gpointer user_data)
{
int n_thread = GPOINTER_TO_INT(user_data);
int n;
GSource *source;
g_print("Starting thread %d\n", n_thread);
for (n = 0; n < N_ITERATIONS; ++n) {
/* If you want to see anything you should add a delay
* to let the main loop update the UI, e.g.:
* g_usleep(g_random_int_range(1234, 567890));
*/
source = g_idle_source_new();
g_source_set_callback(source, update_progress_bar, NULL, NULL);
g_source_attach(source, context);
g_source_unref(source);
}
g_print("Ending thread %d\n", n_thread);
return NULL;
}
gint
main(gint argc, gchar *argv[])
{
GtkWidget *window;
GThread *thread[N_THREADS];
int n;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
bar = gtk_progress_bar_new();
gtk_container_add(GTK_CONTAINER(window), bar);
context = g_main_context_default();
for (n = 0; n < N_THREADS; ++n)
thread[n] = g_thread_new(NULL, thread_func, GINT_TO_POINTER(n));
gtk_widget_show_all(window);
gtk_main();
for (n = 0; n < N_THREADS; ++n)
g_thread_join(thread[n]);
return 0;
}
我有一个简单的多线程 Gtk+2.0 应用程序,它从多个来源(麦克风、网络摄像头、温度传感器)获取数据,并将这些数据显示为屏幕上的图像(网络摄像头帧抓取、麦克风数据表示为示波器渲染、文本等)。
根据我对 Gtk 手册 and various articles 的理解,只有主处理线程应该使用任何影响 UI 的 Gtk functions/calls。但是,main()
入口点会阻塞 gtk_main()
,直到我关闭 UI。除了映射到诸如当我单击 UI 中的按钮或滑块时的事件处理程序之外,似乎唯一对我开放的选项是产生一些 pthread
s 并具有他们在 UI.
我记得很久以前做一些 MFC GUI 开发时应用了类似的原则:只有一个特定的线程应该更新 UI 元素。我如何使用 Gtk+2.0 在 C 中完成此操作?
谢谢。
我会按照您的建议在不同的线程中进行采样。接下来的问题是如何更新 UI。我会做的是使用 'self-pipe'。这通常是为了从信号处理程序进行通信而完成的,但是当其中一个线程不能等待条件变量时,它在线程之间工作得很好。这里做的是建立一个专用管道,在已经拿到数据的线程中向管道写入一个字符,在主程序的select()
循环中在管道的读端写入select()
。详情在这里:Using self-pipe, how can I avoid that the event loop stalls on read()? - useful background on its origins here.
然后您可以让 GTK+ 侦听管道的读取端。您的问题因此减少为让 GTK+ 响应 FD 上的某些内容 - 请参阅 here(第一个答案)了解如何。
根据documentation,主偶数循环可以接受来自不同线程的源:
A GMainContext can only be running in a single thread, but sources can be added to it and removed from it from other threads.
因此您可以通过以下方式从您的工作线程将代码注入 UI 线程:
- 创建
GSource
(例如,使用g_idle_source_new
); - 添加要执行的代码
g-source-set-callback
; - 使用
g_source_attach()
. 将其附加到 UI 线程上下文
这是 GTK+2 和 GLib >= 2.32 的示例程序:
#include <gtk/gtk.h>
#define N_THREADS 100
#define N_ITERATIONS 100
GtkWidget *bar;
GMainContext *context;
static gboolean
update_progress_bar(gpointer user_data)
{
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar),
g_random_double_range(0, 1));
return G_SOURCE_REMOVE;
}
static gpointer
thread_func(gpointer user_data)
{
int n_thread = GPOINTER_TO_INT(user_data);
int n;
GSource *source;
g_print("Starting thread %d\n", n_thread);
for (n = 0; n < N_ITERATIONS; ++n) {
/* If you want to see anything you should add a delay
* to let the main loop update the UI, e.g.:
* g_usleep(g_random_int_range(1234, 567890));
*/
source = g_idle_source_new();
g_source_set_callback(source, update_progress_bar, NULL, NULL);
g_source_attach(source, context);
g_source_unref(source);
}
g_print("Ending thread %d\n", n_thread);
return NULL;
}
gint
main(gint argc, gchar *argv[])
{
GtkWidget *window;
GThread *thread[N_THREADS];
int n;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
bar = gtk_progress_bar_new();
gtk_container_add(GTK_CONTAINER(window), bar);
context = g_main_context_default();
for (n = 0; n < N_THREADS; ++n)
thread[n] = g_thread_new(NULL, thread_func, GINT_TO_POINTER(n));
gtk_widget_show_all(window);
gtk_main();
for (n = 0; n < N_THREADS; ++n)
g_thread_join(thread[n]);
return 0;
}