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_timeout
或 g_timeout_full
(否则 g_idle_add
或 g_timeout_add_seconds
是更好的选择)。
但是,我认为每 20 毫秒更新一次模型并每 50 毫秒显示一次结果没有意义。您只是在浪费计算能力,因为您计算了不会显示的东西。更新模型不应该太昂贵,因为您可以在不到 20 毫秒的时间内完成。因此,如果您不关心确切的时间,只需在连接到 g_idle_add
的回调中更新模型,并在回调结束时,通过在小部件上调用 gtk_widget_queue_draw
或 gtk_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 分钟后)的主要内容,如果未能解决你的问题,请参考以下文章