120

特别是在 Python 中,变量如何在线程之间共享?

尽管我以前使用threading.Thread过,但我从未真正理解或看到变量如何共享的示例。它们是在主线程和子线程之间共享还是仅在子线程之间共享?我什么时候需要使用线程本地存储来避免这种共享?

我已经看到很多关于使用锁在线程之间同步访问共享数据的警告,但我还没有看到一个非常好的问题示例。

提前致谢!

4

6 回答 6

105

在 Python 中,所有东西都是共享的,除了函数局部变量(因为每个函数调用都有自己的一组局部变量,并且线程总是单独的函数调用。)即使那样,只有变量本身(引用对象的名称)是函数的局部;对象本身总是全局的,任何东西都可以引用它们。Thread在这方面,特定线程的对象不是特殊对象。如果您将Thread对象存储在所有线程都可以访问的位置(如全局变量),那么所有线程都可以访问该Thread对象。如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。当然,所有线程都必须共享这个相同的锁,否则它不会很有效。

如果您想要实际的线程本地存储,那就是threading.local进来的地方。threading.local线程之间不共享的属性;每个线程只看到它自己放置在那里的属性。如果您对它的实现感到好奇,源代码在标准库的_threading_local.py中。

于 2008-09-19T19:59:40.587 回答
89

考虑以下代码:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread, local

data = local()

def bar():
    print("I'm called from", data.v)

def foo():
    bar()

class T(Thread):
    def run(self):
        sleep(random())
        data.v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
>> T().start(); T().start()
我是从 Thread-2 调用的
我是从 Thread-1 调用的

这里 threading.local() 被用作一种快速而肮脏的方式来将一些数据从 run() 传递到 bar() 而不更改 foo() 的接口。

请注意,使用全局变量不会成功:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread

def bar():
    global v
    print("I'm called from", v)

def foo():
    bar()

class T(Thread):
    def run(self):
        global v
        sleep(random())
        v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
>> T().start(); T().start()
我是从 Thread-2 调用的
我是从 Thread-2 调用的

同时,如果您能负担得起将这些数据作为 foo() 的参数传递 - 这将是一种更优雅且设计良好的方式:

from threading import Thread

def bar(v):
    print("I'm called from", v)

def foo(v):
    bar(v)

class T(Thread):
    def run(self):
        foo(self.getName())

但在使用第三方或设计不佳的代码时,这并不总是可行的。

于 2009-12-12T18:58:50.557 回答
26

您可以使用创建线程本地存储threading.local()

>>> tls = threading.local()
>>> tls.x = 4 
>>> tls.x
4

存储到 tls 的数据对于每个线程都是唯一的,这将有助于确保不会发生意外共享。

于 2008-09-20T00:31:24.037 回答
3

就像其他所有语言一样,Python 中的每个线程都可以访问相同的变量。“主线程”和子线程之间没有区别。

与 Python 的一个区别是全局解释器锁意味着一次只能有一个线程运行 Python 代码。然而,在同步访问方面,这并没有多大帮助,因为所有常见的抢占问题仍然适用,并且您必须像在其他语言中一样使用线程原语。但是,这确实意味着您需要重新考虑是否使用线程来提高性能。

于 2008-09-19T20:03:30.640 回答
1

我在这里可能错了。如果您知道其他情况,请进行说明,因为这将有助于解释为什么需要使用线程 local()。

这句话似乎不对,没有错:“如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。” 我认为这句话 -> 有效<- 正确,但并不完全准确。我认为术语“原子”意味着 Python 解释器创建了一个字节码块,它没有为 CPU 的中断信号留下空间。

我认为原子操作是不能访问中断的 Python 字节码块。像“running = True”这样的 Python 语句是原子的。在这种情况下,您不需要锁定 CPU 以防止中断(我相信)。Python 字节码分解不会受到线程中断的影响。

像“threads_running[5] = True”这样的 Python 代码不是原子的。这里有两块 Python 字节码;一个用于取消引用对象的 list() 和另一个字节码块以将值分配给对象,在这种情况下是列表中的“位置”。可以引发中断-->在<-两个字节码->块<-之间。那是坏事发生了。

线程 local() 与“原子”有何关系?这就是为什么该声明似乎误导了我。如果不是,你能解释一下吗?

于 2020-02-15T18:26:56.477 回答
1

值得一提threading.local()的是不是单例。

您可以在每个线程中使用更多。它不是一个存储

于 2021-12-22T14:15:00.913 回答