GTK中线程的死锁问题

Posted

技术标签:

【中文标题】GTK中线程的死锁问题【英文标题】:deadlock problem with threads in GTK 【发布时间】:2011-08-21 18:50:29 【问题描述】:

在我的 GUI 中,我的主窗口中有一个列表存储树视图。当用户双击一行时,会弹出一个对话框。问题是我在对话框中填充的数据需要一段时间来处理,所以我所做的是启动一个线程(使用 boost 线程)来进行对话框计算。

In main:
.......
g_signal_connect (G_OBJECT (m_treeview), "row_activated", G_CALLBACK (m_row_activated),
                  (gpointer) main_window);
.......

In m_row_activated:
.........
// combo_box and dialog are GtkWidget* global variables 
create_dialog(dialog, combo_box); // function creates the combobox
set_combo_box_with_loading_message;
gtk_widget_show_all (dialog);
thread m_thread (bind (&do_dialog_calculations, data1, data2, combobox));
.........

In do_dialog_calculations:
.........
// do_calculations takes about 15 seconds to complete
do_calculations(MyData data1, MyData data2, combobox);
gdk_threads_enter();
gtk_combo_box_append_text(...);
gdk_threads_leave()

一切正常(即当用户双击一行时,会立即弹出一个带有加载消息的对话框,并最终在线程返回时填充),但我的问题是当用户在 do_dialog_calculations 中的 do_calculations 之前关闭对话框时完成。如果对话框被破坏,我在其中的组合框将被破坏,并且我对 gtk_combo_box_append_text 的调用将出现段错误。

我尝试在更新组合框之前对其进行测试:

In do_dialog_calculations:
.........
do_calculations(MyData data1, MyData data2, combobox);
gdk_threads_enter();
if (GTK_IS_COMBO_BOX (combobox))
   gtk_combo_box_append_text(...);
gdk_threads_leave()

但这会导致调用 GTK_IS_COMBO_BOX 时出现死锁。我认为这是因为 GTK_IS_COMBO_BOX 可能会调用 gdk_threads_enter()。我也尝试过测试 NULL

 if (combobox == NULL)

但这似乎也不起作用。有关如何解决此问题的任何建议?

更新:GTK_IS_COMBO_BOX 的死锁只有在我打开对话框后立即关闭时才会发生(即在 do_calculations() 完成之前。如果我只是让对话框坐下,它最终会更新。另外,如果我之前切换组合框检查编写调用 gdk_threads_enter():

if (GTK_IS_COMBO_BOX (combobox)

   gdk_threads_enter();
   gtk_combo_box_append_text(...);
   gdk_threads_leave();

在执行此代码之前销毁对话框时不会发生死锁。但是,我担心用户在 GTK_IS_COMBO_BOX 检查完成后关闭对话框的可能性很小。

PS - 我使用线程来进行对话框计算,因为对话框是非模态的,我希望用户能够在填充对话框时使用主 UI 执行其他操作。

【问题讨论】:

【参考方案1】:
我认为这是因为 GTK_IS_COMBO_BOX 可能调用了 gdk_threads_enter()

我认为情况并非如此。这些宏通常非常简单,我不希望它锁定。事实上,据我所知,gdk_threads_enter 的整个想法是库本身不应该调用它,只有知道它在另一个线程中运行的代码才应该调用它。

这是我的想法:您是否忘记致电g_thread_initgdk_threads_init

另外,要记住一件事...默认情况下,gdk_threads_enter 不使用递归互斥锁。尽管有些人对递归互斥锁有宗教上的反对意见,但gdk_threads_enter 可以使用一个:

static GStaticRecMutex my_gdk_lock;

static void my_gdk_lock_enter() g_static_rec_mutex_lock(&my_gdk_lock);
static void my_gdk_lock_leave() g_static_rec_mutex_unlock(&my_gdk_lock);

// ...

   g_thread_init(NULL);

   g_static_rec_mutex_init(&my_gdk_lock);

   gdk_threads_set_lock_functions(G_CALLBACK(my_gdk_lock_enter),
                                  G_CALLBACK(my_gdk_lock_leave));

   gdk_threads_init();

// ...

更新:从您的评论看来,您在销毁对话框和填充组合框之间存在竞争条件。一种可能的解决方案是增加组合框的引用计数(即gtk_widget_ref),这样当你的异步工作者正在做某事时它就不会被释放。然后当其他线程不再需要指针时用gtk_widget_unref释放它。

【讨论】:

感谢 asveikau 的快速回复!在调用 gtk_main() 之前,我确实调用了 g_thread_init 和 gdk_threads_init:g_thread_init (NULL); gdk_threads_init(); gtk_init (&argc, &argv); ....... gdk_threads_enter(); gtk_main(); gdk_threads_leave();有趣的是,死锁只发生在我在检查 GTK_IS_COMBO_BOX 之前销毁对话框时。例如,当我双击一行时,会弹出对话框并(大约 15 秒后)填充。但是如果我双击该行并立即销毁对话框,它会挂在 GTK_IS_COMBO_BOX 检查中。 @Tim - 阅读您的评论我想我看到了另一个问题。我已经更新了答案。

以上是关于GTK中线程的死锁问题的主要内容,如果未能解决你的问题,请参考以下文章

Java中线程死锁问题

[锁] 线程死锁解析

多线程编程之线程死锁问题

JVM如何查看线程死锁

Linux下面 多线程死锁问题的调试

怎么处理JAVA多线程死锁问题?