从其他线程执行 GTK 函数

Ale*_*iev 5 multithreading gtk3

这个问题是关于GTK和线程的。如果您的应用程序崩溃、冻结或者您想要一个多线程 GTK 应用程序,您可能会发现它很有用。

Ale*_*iev 5

主循环

\n

为了理解 GTK,您必须理解 2 个概念。

\n
    \n
  1. 所有现代 GUI 都是单线程的。它们有一个线程处理来自窗口系统的事件(如按钮、鼠标事件)。\n这样的线程称为主事件循环或主循环。\nGTK 也是单线程的,而不是MT 安全的。这意味着,您不能从其他线程调用任何 GTK 函数,因为这会导致未定义的行为。

    \n
  2. \n
  3. 正如 Gtk 文档所述,

    \n
  4. \n
\n
\n

与所有 GUI 工具包一样,GTK+ 使用事件驱动的编程模型。当用户什么都不做时,GTK+ 位于 \xe2\x80\x9cmain 循环\xe2\x80\x9d 中并等待输入。如果用户执行某些操作(例如单击鼠标),则主循环 \xe2\x80\x9c 会唤醒 \xe2\x80\x9d 并向 GTK+ 传递事件。GTK+ 将事件转发到一个或多个小部件。

\n
\n

Gtk 是基于事件的、异步的。它对按钮点击的反应不是在点击的那一刻,而是稍后。

\n

它可以非常粗略地写成这样(不要在家尝试这个):

\n
static list *pollable;\n\nint main_loop (void)\n{\n    while (run)\n        {\n            lock_mutex()\n            event_list = poll (pollable); // check whether there are some events to react to\n            unlock_mutex()\n            dispatch (event_list);        // react to events.\n        }\n}\n\nvoid schedule (gpointer function)\n{\n    lock_mutex()\n    add_to_list (pollable, something);\n    unlock_mutex()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我想要在我的应用程序中执行延迟操作

\n

例如,在几秒钟内隐藏工具提示或更改按钮文本。\n假设您的应用程序是单线程的,如果您调用sleep()它将在主循环中执行。\nsleep()意味着该特定线程将挂起指定的秒数。不会完成任何工作。\n如果该线程是主线程,GTK 将无法重绘或对用户交互做出反应。应用程序冻结。

\n

你应该做的是安排函数调用。可以使用g_timeout_add或 来完成g_idle_add\n在第一种情况下,我们的poll()上面的代码片段将在几秒钟内返回此事件。在后一种情况下,当没有更高优先级的事件时,它将返回。

\n
static int count;\n\ngboolean change_label (gpointer data)\n{\n    GtkButton *button = data;\n    gchar *text = g_strdup_printf ("%i seconds left", --count);\n    if (count == 0)\n        return G_SOURCE_REMOVE;\n    return G_SOURCE_CONTINUE; \n}\n\nvoid button_clicked (GtkButton *button)\n{\n    gtk_button_set_label (button, "clicked");\n    count = 5;\n    g_timeout_add (1 * G_TIME_SPAN_SECOND, change_label, button);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

从函数返回值非常重要。如果您不这样做,则行为未定义,您的任务可能会被再次调用或删除。

\n

我有一个长期运行的任务

\n

长时间运行的任务与调用没有什么不同sleep。当一个线程忙于该任务时,它显然无法执行任何其他任务。如果那是一个GUI线程,它不能重绘界面。这就是为什么您应该将所有长时间运行的任务移至其他线程。不过有一个例外:非阻塞 IO,但这超出了我的回答主题。

\n

我有额外的线程并且我的应用程序崩溃了

\n

正如已经提到的,GTK 不是 MT 安全的。您不得从其他线程调用 Gtk 函数。\n您必须安排执行。g_timeout_add与其他 GTK 函数不同,并且g_idle_add MT 安全的。\n该回调将在主循环中执行。如果回调和线程之间有一些共享资源,则必须以原子方式读取/写入它们或使用互斥体。

\n
static int data;\nstatic GMutex mutex;\n\ngboolean change_label (gpointer data)\n{\n    GtkButton *button = data;\n    int value;\n    gchar *text;\n\n    // retrieve data\n    g_mutex_lock (&mutex);\n    value = data;\n    g_mutex_unlock (&mutex);\n\n    // update widget\n    text = g_strdup_printf ("Current data value: %i", value);\n    return G_SOURCE_REMOVE;\n}\n\ngpointer thread_func (gpointer data)\n{\n    GtkButton *button = data;\n    while (TRUE)\n        {\n            sleep (rand_time);\n            g_mutex_lock (&mutex);\n            ++data;\n            g_mutex_unlock (&mutex);\n            g_idle_add (change_label, button);\n        }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

确保尽可能少地保留互斥体。想象一下,您在另一个线程中锁定互斥体并执行一些 IO。主循环将被卡住,直到互斥体被释放。有g_mutex_try_lock()会立即返回,但它可能会带来额外的同步问题,因为您无法保证当主循环尝试锁定互斥体时,互斥体将被解锁。

\n

跟进:但是python是单线程的,还有GIL等等?

\n

你可以想象Python是在单核机器上运行的多线程应用程序。\n你永远不知道线程何时会切换。您调用 GTK 函数,但不知道主循环处于哪种状态。也许它刚刚释放了资源。总是安排。

\n

未讨论的内容和进一步阅读

\n\n