【问题标题】:Is it safe to use a Intvar/DoubleVar in a Python thread?在 Python 线程中使用 Intvar/DoubleVar 是否安全?
【发布时间】:2014-10-10 16:32:16
【问题描述】:

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

所以,问题是:从主循环以外的任何地方访问/编辑IntVar()DoubleVar() 等是否安全?另外,从单独的线程中简单地读取值(通过 x.get() )怎么样?我知道不应该从单独的线程编辑/更新小部件,但我没有找到有关 Intvars 等的信息。任何见解将不胜感激。

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

Python/Tkinter: Are Tkinter StringVar (IntVar, etc) thread safe?

【问题讨论】:

  • 对于任何可能遇到这种情况的人,我强烈建议不要在线程中使用IntVars、DoubleVars 等。我在 Python 3.3 中做了一些测试,结果非常不一致。我修改了一个我最近完成测试的填充程序,在线程中使用IntVarDoubleVar 会导致它崩溃。最令人不安的是,它的表现出人意料。例如,我最初测试了 100 个填充周期没有任何问题,然后再次运行它,只是在第 23 个周期崩溃。我运行了几十个这样的测试,其中大部分导致崩溃。
  • 这种不可预测性正是不是线程安全的意思。

标签: python multithreading python-3.x tkinter


【解决方案1】:

基于_tkinter 模块的comments in the source code,只要Tcl 是使用--enable-threads 选项构建的,tkinter 似乎实际上至少意图是线程安全的。这得到了 Python 跟踪器 (issue11077) 上已解决的错误的支持,该错误指出 tkinter 不是线程安全的,最终确定 tkinter 的所有线程安全问题都是在 Python 2.7.3 中修复的错误+

_tkinter 模块的消息来源对此问题的看法如下:

Tcl 解释器仅在创建它的线程中有效,并且所有 Tk 活动也必须在此线程中发生。这意味着必须在创建解释器的线程中调用主循环。 可以从其他线程调用命令; _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)

当您set 一个变量时,它会在与Variable 关联的主窗口小部件上调用globalsetvar 方法。 _tk.globalsetvar 方法is implemented in 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);
}

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

【讨论】:

    猜你喜欢
    • 2011-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多