【问题标题】:Safety of using pthreads in Gtk+2.0 application在 Gtk+2.0 应用程序中使用 pthread 的安全性
【发布时间】:2023-03-31 16:42:01
【问题描述】:

我有一个简单的多线程 Gtk+2.0 应用程序,它从多个来源(麦克风、网络摄像头、温度传感器)获取数据,并将这些数据作为图像显示在屏幕上(网络摄像头帧抓取、麦克风数据表示为示波器渲染、文本、等)。

根据 Gtk 手册 and various articles 我的理解,只有主处理线程应该使用任何影响 UI 的 Gtk 函数/调用。但是,main() 入口点会阻塞 gtk_main(),直到我关闭 UI。除了映射到诸如单击 UI 中的按钮或滑块之类的事件处理程序之外,似乎对我开放的唯一选择是生成一些 pthreads 并让它们定期采样UI 中屏幕上的数据和更新信息。

我记得很久以前做过一些 MFC GUI 开发,应用了类似的原则:只有一个特定的线程应该更新 UI 元素。如何在 C 中使用 Gtk+2.0 完成此操作?

谢谢。

【问题讨论】:

    标签: c multithreading gtk pthreads


    【解决方案1】:

    根据documentation,主偶数循环可以接受来自不同线程的源:

    GMainContext 只能在单个线程中运行,但源可以 被添加到它并从其他线程中删除。

    因此,您可以通过以下方式将代码从工作线程注入 UI 线程:

    1. 创建GSource(例如,通过使用g_idle_source_new);
    2. g-source-set-callback添加你要执行的代码;
    3. 使用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 + pthreads 设置来完成这个吗?谢谢!
    • @Dogbert 刚刚添加到答案中。
    • 谢谢。这最符合我最初的问题的意思。
    【解决方案2】:

    按照您的建议,我会在单独的线程中进行采样。那么问题是如何更新 UI。我要做的是使用“自管”。通常这样做是为了从信号处理程序进行通信,但是当其中一个线程不能等待条件变量时,它在线程之间工作得很好。你在这里做的是设置一个专用管道,在已经获取数据的线程中向管道中写入一个字符,并在主程序的select()循环中管道的读取端select()。详细信息在这里:Using self-pipe, how can I avoid that the event loop stalls on read()? - 关于其起源的有用背景here

    然后您可以让 GTK+ 在管道的读取端进行侦听。因此,您的问题被简化为让 GTK+ 响应 FD 上的某些内容 - 请参阅here(第一个答案)了解如何。

    【讨论】:

    • 我只熟悉GTK+ 3。GTK+ 2没有gdk_threads_idle_add()吗?
    • @andlabs 我不知道这一点(谷歌似乎也几乎不知道)。我想我正在回答一个更普遍的问题,即如何在线程和为传统非线程程序编写的事件循环之间进行通信。
    • 问题是,这并没有完全解决main 线程在gtk_main() 函数被阻止时应该如何更新UI。单独的线程可以更新 UI 吗?我的所有 UI 更新调用都用互斥锁包装,因此没有两个线程尝试同时更新 UI。
    • 上面的第二个链接没有说明吗?
    • @abligh 对;我要说的是,当它不做任何事情时,该函数将是您如何将一些代码排队在主线程上运行(因此将是如何进行通信,除了同步)。您在答案中描述的内容似乎有点混乱,仅出于更新 GUI 的目的,但我不知道 GTK+ 2,所以我无法真正发表评论......
    猜你喜欢
    • 1970-01-01
    • 2015-12-22
    • 2011-11-03
    • 2012-08-14
    • 2011-07-04
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多