C语言结合GTK3如何实现不中断运行的线程以完成骰子滚动动画?
核心问题原因
- GTK 框架的所有GUI操作接口仅允许在运行GTK主循环(
gtk_main)的主线程中调用,本身不支持跨线程操作GUI。你在自定义的pthread线程中直接调用gtk_widget_set_sensitive、gtk_image_set_from_pixbuf修改控件状态,完全违反了GTK的线程安全约定,这就是程序随机崩溃的根本原因,和是否加互斥锁无关。 - 额外隐患:连续点击Roll按钮会重复创建线程,覆盖全局的
thread变量,导致之前创建的线程资源泄漏、无法正常回收。
修复方案
不需要自行创建线程,直接使用GTK内置的定时回调接口g_timeout_add即可实现间隔触发的需求,定时回调默认在主线程执行,可以安全操作所有GUI控件,代码更简洁也没有线程安全问题。
修正后完整代码
#include <gtk/gtk.h> #include <stdio.h> #include <stdlib.h> #include <time.h> GtkWidget *window; GtkWidget *vbox; GtkWidget *butt; GtkWidget *image; GdkPixbuf *pixbuf1; GdkPixbuf *pixbuf2; GdkPixbuf *pixbuf3; GdkPixbuf *pixbuf4; GdkPixbuf *pixbuf5; GdkPixbuf *pixbuf6; int roll_count = 0; guint timer_id = 0; // 定时回调函数,每次返回G_SOURCE_CONTINUE继续执行,返回G_SOURCE_REMOVE销毁定时器 gboolean roll_dice_cb(gpointer user_data) { if (roll_count >= 4) { // 滚动结束,恢复按钮可用,销毁定时器 gtk_widget_set_sensitive(butt, TRUE); timer_id = 0; return G_SOURCE_REMOVE; } int j = rand() % 6 + 1; switch (j) { case 1: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf1); break; case 2: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf2); break; case 3: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf3); break; case 4: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf4); break; case 5: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf5); break; default: gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf6); } roll_count++; return G_SOURCE_CONTINUE; } void start_roll(GtkWidget *widget, gpointer user_data) { // 避免重复触发 if (timer_id != 0) return; gtk_widget_set_sensitive(butt, FALSE); roll_count = 0; // 注册400ms触发一次的定时器 timer_id = g_timeout_add(400, roll_dice_cb, NULL); } int main (int argc, char *argv[]) { gtk_init(&argc, &argv); srand(time(0)); pixbuf1 = gdk_pixbuf_new_from_file("dice_1_48x48.png", NULL); pixbuf2 = gdk_pixbuf_new_from_file("dice_2_48x48.png", NULL); pixbuf3 = gdk_pixbuf_new_from_file("dice_3_48x48.png", NULL); pixbuf4 = gdk_pixbuf_new_from_file("dice_4_48x48.png", NULL); pixbuf5 = gdk_pixbuf_new_from_file("dice_5_48x48.png", NULL); pixbuf6 = gdk_pixbuf_new_from_file("dice_6_48x48.png", NULL); image = gtk_image_new(); gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf1); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_window_set_default_size(GTK_WINDOW(window), 150, 100); butt = gtk_button_new_with_label("Roll"); g_signal_connect(G_OBJECT(butt), "clicked", G_CALLBACK(start_roll), NULL); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL,10); gtk_box_pack_start(GTK_BOX(vbox),image,0,0,0); gtk_box_pack_start(GTK_BOX(vbox),butt,0,0,0); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show_all(window); gtk_main(); // 退出前如果定时器还在运行就销毁 if (timer_id != 0) { g_source_remove(timer_id); } // 释放pixbuf资源 g_object_unref(pixbuf1); g_object_unref(pixbuf2); g_object_unref(pixbuf3); g_object_unref(pixbuf4); g_object_unref(pixbuf5); g_object_unref(pixbuf6); return 0; }
编译指令保持不变:gcc -rdynamic -o test3d1 test3d1.c $(pkg-config --cflags --libs gtk+-3.0)
内容的提问来源于stack exchange,提问作者Graeme Jensz
相关产品推荐
相关产品推荐

