在 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 中的按钮或滑块时的事件处理程序之外,似乎唯一对我开放的选项是产生一些 pthreads 并具有他们在 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 线程:

  1. 创建 GSource(例如,使用 g_idle_source_new);
  2. 添加要执行的代码 g-source-set-callback;
  3. 使用 g_source_attach().
  4. 将其附加到 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;
}