在 Python 线程中使用 Intvar/DoubleVar 是否安全?

Ell*_*lle 3 python multithreading tkinter python-3.x

让我先说一下,我几乎毫无疑问会Queue在我的程序中使用 a 。在花了相当多的时间研究这个主题而没有找到任何结论性的答案之后,我发布这个问题或多或少只是为了满足我的好奇心。

因此,问题:是否可以安全访问/修改的IntVar()DoubleVar()比主回路等,从任何地方其他?另外,x.get()从单独的线程中简单地读取值 (via ) 怎么样?我知道不应从单独的线程编辑/更新小部件,但我没有发现有关 Intvars 等的信息。任何见解将不胜感激。

这是一个从未真正回答过的相关(但相当古老)的问题:

Python/Tkinter:Tkinter StringVar(IntVar 等)线程安全吗?

dan*_*ano 7

基于源代码的注释_tkinter模块,它似乎像Tkinter的实际至少意图是线程安全的,只要Tcl的与内置--enable-threads选项。这得到了 Python 跟踪器 ( issue11077 )上已解决错误的支持,该错误指出tkinter不是线程安全的,最终确定 tkinter 的所有线程安全问题都是在 Python 2.7.3+ 中修复的错误

以下是该_tkinter模块的消息来源对该问题的说明:

Tcl 解释器仅在创建它的线程中有效,并且所有 Tk 活动也必须在该线程中发生。这意味着必须在创建解释器的线程中调用 mainloop。可以从其他线程调用命令;_tkinter 将为解释器线程排队一个事件,然后解释器线程将执行命令并将结果传回。如果主线程不在主循环中,调用命令导致异常;如果主循环正在运行但未处理事件,则命令调用将被阻止。

因此,只要主循环在应用程序的主线程中主动运行,tkinter 就会自动调度该方法在主线程中运行,这将使其成为线程安全的。也就是说,除了实际的 Tkinter 源代码和上述错误报告外,互联网上的大多数来源都表明,将 tkinter 与线程一起使用会导致崩溃。我不太确定该相信什么,尽管在我尝试的一些小示例中,从线程更新 GUI 工作正常。

现在,您特别想知道与 Tk 小部件相关的线程安全规则是否也适用于Variable子类。它确实:这是 的一些实现Variable,其父级IntVar

class Variable:

    _default = ""
    _tk = None
    def __init__(self, master=None, value=None, name=None):
        """Construct a variable

        MASTER can be given as master widget.
        VALUE is an optional value (defaults to "")
        NAME is an optional Tcl name (defaults to PY_VARnum).

        If NAME matches an existing variable and VALUE is omitted
        then the existing value is retained.
        """
        # ...snip...
        if not master:
            master = _default_root
        self._master = master
        self._tk = master.tk

    def set(self, value):
        """Set the variable to VALUE."""
        return self._tk.globalsetvar(self._name, value)
Run Code Online (Sandbox Code Playgroud)

当您set使用变量时,它会调用globalsetvarVariable. 该_tk.globalsetvar方法在 C 中实现,并在内部调用var_invoke,后者在内部调用WaitForMainLoop,这将尝试安排在主线程中执行的命令,如_tkinter我上面包含的来源的引用中所述。

static PyObject*
var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
{
       /* snip */

        /* The current thread is not the interpreter thread.  Marshal
           the call to the interpreter thread, then wait for
           completion. */
        if (!WaitForMainloop(self))
            return NULL;
        /* snip */


static PyObject *
Tkapp_GlobalSetVar(PyObject *self, PyObject *args)
{
    return var_invoke(SetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
}
Run Code Online (Sandbox Code Playgroud)

请注意,此代码路径也用于 get 操作,因此setget操作都受相同规则的约束。