GTK+3 GUI 随机冻结(1 小时后或 20 分钟后)

Posted

技术标签:

【中文标题】GTK+3 GUI 随机冻结(1 小时后或 20 分钟后)【英文标题】:GTK+3 GUI freezes randomly (after 1 hour or after 20 minutes) 【发布时间】:2018-05-06 15:44:39 【问题描述】:

我正在用 C 语言开发一个程序,它使用 GTK+3 并遵循 - 或多或少 - MVC 架构:

模型每 20 毫秒通过调用 model_update 更新一次(它不调用 GTK 函数); GUI 通过读取模型变量每 50 毫秒调用一次 gui_update 进行更新。

我的问题是 GUI 在随机运行时间后冻结,可能是 20 分钟或超过 1 小时,我不知道为什么。也许我应该知道一些关于 GTK 的事情?

注意: 使用互斥锁可以保护对模型变量的访问。

非常感谢您的帮助!!

#include <signal.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/prctl.h> 

void *
thread_gui(void* data)
 
    g_timeout_add(50, handler_timer_gui_update, NULL); // updates the GUI each 50ms
    gtk_main();
    pthread_exit(NULL);


gint
handler_timer_gui_update(gpointer data)

    gui_update();
    // gui_update reads the model and updates GUI by using
    // gtk_label_set_text, gtk_spin_button_set_value, cairo_paint
    return TRUE;


void
launch_periodical_call_updating_model( )

    signal( SIGRTMIN + 1, model_update );

    timer_t timer;
    struct sigevent event;
    event.sigev_notify = SIGEV_SIGNAL;
    event.sigev_signo = SIGRTMIN + 1;
    event.sigev_value.sival_ptr = &timer;
    timer_create(CLOCK_REALTIME, &event, &timer);

    struct itimerspec spec;
    spec.it_value.tv_nsec = 20 * 1000000; // updates the model each 20 ms
    spec.it_value.tv_sec = 0;
    spec.it_interval = spec.it_value;

    timer_settime( timerModel, 0, &spec, NULL);


int
main( int argc, char *argv[] )
 
    pthread_t pthread_gui;

    init_model( ); // init model variables
    launch_periodical_call_updating_model( ); 

    // signal capture to exit
    signal( SIGINT, ctrlc_handler );
    signal( SIGTERM, ctrlc_handler );

    // GUI
    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();
    gtk_init( &argc, &argv );
    create_gui ( ); // building GTK Window with widgets  
    pthread_create(&pthread_gui, NULL, thread_gui, NULL);
    gdk_threads_leave();

    // Leaving the program
    pthread_join( pthread_gui, NULL ); 
    stop_model( ); //It stops to update the model and releases memory

    return 0;


void
ctrlc_handler( int sig )

    gtk_main_quit(); 
 

【问题讨论】:

尝试在信号处理程序中获取互斥锁是一个非常糟糕的主意:( 【参考方案1】:

您不应该在 GTK 中使用 Unix 信号。见signal-safety(7) 和signal(7)。

您应该考虑只使用 Glib 计时器(而不是使用任何 POSIX 计时器)。阅读Glib event loop(用于GTK)并使用g_timeout_add或相关函数。

如果您坚持要在 GTK 程序中处理 Unix 信号,请对它们执行Qt recommends 的处理:在初始化时为 self 设置一个 pipe(7),并轮询该文件描述符(在 GTK 中,使用 g_source_add_unix_fd 或 g_io_channel_unix_new ) 您的 Unix 信号处理程序将 write(2) (一次一个或很少几个字节)。 BTW,Linux 也提供signalfd(2)。

最后,GTK 并不是真正的线程友好的(另请参阅this),所有 GTK 函数都应该(仅)从您的主线程(而不是其他一些 pthread_gui)调用。实际上,如果您确实需要使用多个线程进行编码,请考虑让您的非 GUI 线程也通过管道与 GTK 进行通信和同步,并使用Glib thread functions 来启动和管理它们。

您甚至可以考虑一些多处理方法:让 GUI 程序在一个进程中,而另一个处理(多线程)在另一个进程中(可能由 GUI 程序使用 g_spawn_async_with_pipes 启动)。

了解continuations 和continuation-passing style。它可能会帮助您从这些方面进行思考。

【讨论】:

现在完美运行!!我使用 g_timeout_add 而不是信号函数...性能还不错 =) “continuations”乍一看很奇怪。【参考方案2】:

您应该停止使用信号、线程,而只使用 GTK+ 主循环的事件源。因此,如果您想每 50 毫秒调用一次代码,那么如果您需要每秒刷新一次以上 UI,只需使用 g_timeoutg_timeout_full(否则 g_idle_addg_timeout_add_seconds 是更好的选择)。

但是,我认为每 20 毫秒更新一次模型并每 50 毫秒显示一次结果没有意义。您只是在浪费计算能力,因为您计算了不会显示的东西。更新模型不应该太昂贵,因为您可以在不到 20 毫秒的时间内完成。因此,如果您不关心确切的时间,只需在连接到 g_idle_add 的回调中更新模型,并在回调结束时,通过在小部件上调用 gtk_widget_queue_drawgtk_widget_queue_draw_area 来要求更新 UI需要重新绘制。然后它将尽可能快地计算(因此可以适应每台计算机的性能),并且不会计算未显示的内容。然后使用计时器确定两次刷新之间经过的时间。如果太高,可以使用g_timeout_add而不是g_idle_add来限制刷新率,使用更长的刷新周期。

【讨论】:

我现在明白为什么我从信号函数中调用 gtk_label_set_text 或 gtk_spin_button_set_value 会遇到一些麻烦...... gtk_widget_queue_draw 似乎是更新 cairo 表面的好方法,我要检查一下! 我确认嘿嘿它有效!...而且它比定期调用更好 cairo = gdk_cairo_create(gtk_widget_get_window(drawing_area));绘图功能(开罗); cairo_destroy(cairo); 是的,如果您使用的是 GTK 3,您应该在连接到“draw”信号的回调中进行绘图。你被传递了一个cairo_t *,它是那里的开罗上下文,所以不需要自己创建或销毁开罗上下文。请参阅文档的“自定义绘图”部分:developer.gnome.org/gtk3/stable/ch01s05.html

以上是关于GTK+3 GUI 随机冻结(1 小时后或 20 分钟后)的主要内容,如果未能解决你的问题,请参考以下文章

Kivy GUI 冻结

GTK3 / Glib 重复一个函数

PyQt:如何在不冻结 GUI 的情况下更新进度?

即使我在单独的线程中运行,QT GUI 也会冻结

GtK+3.0 多线程应用

图 GUI 冻结