multithreading - 从其他线程执行 GTK 函数
问题描述
这个问题是关于 GTK 和线程的。如果您的应用程序崩溃、冻结或您想要拥有一个多线程 GTK 应用程序,您可能会发现它很有用。
解决方案
主循环
要了解 GTK,您必须了解 2 个概念。
所有当代 GUI 都是单线程的。他们有一个线程来处理来自窗口系统的事件(如按钮、鼠标事件)。这样的线程称为主事件循环或主循环。GTK 也是单线程的,不是MT 安全的。这意味着,您不能从其他线程调用任何 GTK 函数,因为它会导致未定义的行为。
正如 Gtk 文档所述,
与所有 GUI 工具包一样,GTK+ 使用事件驱动的编程模型。当用户什么都不做时,GTK+ 位于“主循环”并等待输入。如果用户执行了一些动作——比如说,鼠标点击——然后主循环“唤醒”并将一个事件传递给 GTK+。GTK+ 将事件转发到一个或多个小部件。
Gtk 是基于事件和异步的。它对按钮点击的反应不是在点击的确切时刻,而是稍后。
可以很粗略地写成这样(不要在家里尝试这个):
static list *pollable;
int main_loop (void)
{
while (run)
{
lock_mutex()
event_list = poll (pollable); // check whether there are some events to react to
unlock_mutex()
dispatch (event_list); // react to events.
}
}
void schedule (gpointer function)
{
lock_mutex()
add_to_list (pollable, something);
unlock_mutex()
}
我想在我的应用程序中执行延迟操作
例如,在几秒钟内隐藏工具提示或更改按钮文本。假设您的应用程序是单线程的,如果您调用sleep()
它将在主循环中执行。
sleep()
意味着,这个特定的线程将被挂起指定的秒数。不会做任何工作。如果这个线程是主线程,GTK 将无法重绘或对用户交互做出反应。应用程序冻结。
你应该做的是安排函数调用。可以使用g_timeout_add
或g_idle_add
在第一种情况下,我们上面的poll()
from 代码段将在几秒钟内返回此事件。在后一种情况下,它将在没有更高优先级的事件时返回。
static int count;
gboolean change_label (gpointer data)
{
GtkButton *button = data;
gchar *text = g_strdup_printf ("%i seconds left", --count);
if (count == 0)
return G_SOURCE_REMOVE;
return G_SOURCE_CONTINUE;
}
void button_clicked (GtkButton *button)
{
gtk_button_set_label (button, "clicked");
count = 5;
g_timeout_add (1 * G_TIME_SPAN_SECOND, change_label, button);
}
从函数返回值非常重要。如果您不这样做,则行为未定义,您的任务可能会被再次调用或删除。
我有一个长期运行的任务
长时间运行的任务与调用sleep
. 当一个线程忙于那个任务时,它显然不能执行任何其他任务。如果那是一个GUI线程,它不能重绘界面。这就是为什么您应该将所有长时间运行的任务移动到其他线程。不过有一个例外:非阻塞 IO,但这超出了我的回答范围。
我有额外的线程并且我的应用程序崩溃了
如前所述,GTK 不是 MT 安全的。你不能从其他线程调用 Gtk 函数。您必须安排执行。g_timeout_add
并且g_idle_add
是MT 安全的,与其他 GTK 函数不同。该回调将在主循环中执行。如果您在回调和线程之间有一些共享资源,则必须以原子方式读取/写入它们或使用互斥锁。
static int data;
static GMutex mutex;
gboolean change_label (gpointer data)
{
GtkButton *button = data;
int value;
gchar *text;
// retrieve data
g_mutex_lock (&mutex);
value = data;
g_mutex_unlock (&mutex);
// update widget
text = g_strdup_printf ("Current data value: %i", value);
return G_SOURCE_REMOVE;
}
gpointer thread_func (gpointer data)
{
GtkButton *button = data;
while (TRUE)
{
sleep (rand_time);
g_mutex_lock (&mutex);
++data;
g_mutex_unlock (&mutex);
g_idle_add (change_label, button);
}
}
确保尽可能少地持有互斥锁。想象一下,您将一个互斥锁锁定在另一个线程中并执行一些 IO。主循环将被卡住,直到互斥锁被释放。有g_mutex_try_lock()
一个立即返回,但它会带来额外的同步问题,因为你不能保证当 mainloop 尝试锁定互斥锁时它会被解锁。
跟进:但是python是单线程的,GIL等等?
你可以想象 python 是在单核机器上运行的多线程应用程序。你永远不知道线程什么时候会被切换。您调用了 GTK 函数,但您不知道主循环处于哪个状态。也许它刚刚释放了资源。总是安排。
未讨论的内容和进一步阅读
- 关于 glib 主循环的详细文档可以在这里找到
GSource
作为更底层的原语。GTask
推荐阅读
- python-3.x - Python/Kivy - 将变量从一个类发送到另一个类
- python - "".join(e[0] for e in phrase.split()) 的幕后发生了什么?
- kubernetes - 如何在 Kubernetes 上为有状态应用程序设置 Websphere Liberty 集群
- python - 使用 RGB 值调用 koalas.hist() 时如何更改颜色
- ruby-on-rails - 如何从控制器关注点调用模型
- java - 在信号量中调用 wait() 之前打印
- laravel - Laravel:返回更新后的模型和额外的数据透视
- php - 如何从我在 apache2 上的网页中删除 .php 文件扩展名
- r - 条件连接多个表覆盖而不是创建新列
- c++ - 更正代码 c++ 指针和删除