特别是在 Python 中,变量如何在线程之间共享?
尽管我以前使用threading.Thread
过,但我从未真正理解或看到变量如何共享的示例。它们是在主线程和子线程之间共享还是仅在子线程之间共享?我什么时候需要使用线程本地存储来避免这种共享?
我已经看到很多关于使用锁在线程之间同步访问共享数据的警告,但我还没有看到一个非常好的问题示例。
提前致谢!
特别是在 Python 中,变量如何在线程之间共享?
尽管我以前使用threading.Thread
过,但我从未真正理解或看到变量如何共享的示例。它们是在主线程和子线程之间共享还是仅在子线程之间共享?我什么时候需要使用线程本地存储来避免这种共享?
我已经看到很多关于使用锁在线程之间同步访问共享数据的警告,但我还没有看到一个非常好的问题示例。
提前致谢!
在 Python 中,所有东西都是共享的,除了函数局部变量(因为每个函数调用都有自己的一组局部变量,并且线程总是单独的函数调用。)即使那样,只有变量本身(引用对象的名称)是函数的局部;对象本身总是全局的,任何东西都可以引用它们。Thread
在这方面,特定线程的对象不是特殊对象。如果您将Thread
对象存储在所有线程都可以访问的位置(如全局变量),那么所有线程都可以访问该Thread
对象。如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。当然,所有线程都必须共享这个相同的锁,否则它不会很有效。
如果您想要实际的线程本地存储,那就是threading.local
进来的地方。threading.local
线程之间不共享的属性;每个线程只看到它自己放置在那里的属性。如果您对它的实现感到好奇,源代码在标准库的_threading_local.py中。
考虑以下代码:
#/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())
但在使用第三方或设计不佳的代码时,这并不总是可行的。
您可以使用创建线程本地存储threading.local()
。
>>> tls = threading.local()
>>> tls.x = 4
>>> tls.x
4
存储到 tls 的数据对于每个线程都是唯一的,这将有助于确保不会发生意外共享。
就像其他所有语言一样,Python 中的每个线程都可以访问相同的变量。“主线程”和子线程之间没有区别。
与 Python 的一个区别是全局解释器锁意味着一次只能有一个线程运行 Python 代码。然而,在同步访问方面,这并没有多大帮助,因为所有常见的抢占问题仍然适用,并且您必须像在其他语言中一样使用线程原语。但是,这确实意味着您需要重新考虑是否使用线程来提高性能。
我在这里可能错了。如果您知道其他情况,请进行说明,因为这将有助于解释为什么需要使用线程 local()。
这句话似乎不对,没有错:“如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。” 我认为这句话 -> 有效<- 正确,但并不完全准确。我认为术语“原子”意味着 Python 解释器创建了一个字节码块,它没有为 CPU 的中断信号留下空间。
我认为原子操作是不能访问中断的 Python 字节码块。像“running = True”这样的 Python 语句是原子的。在这种情况下,您不需要锁定 CPU 以防止中断(我相信)。Python 字节码分解不会受到线程中断的影响。
像“threads_running[5] = True”这样的 Python 代码不是原子的。这里有两块 Python 字节码;一个用于取消引用对象的 list() 和另一个字节码块以将值分配给对象,在这种情况下是列表中的“位置”。可以引发中断-->在<-两个字节码->块<-之间。那是坏事发生了。
线程 local() 与“原子”有何关系?这就是为什么该声明似乎误导了我。如果不是,你能解释一下吗?
值得一提threading.local()
的是不是单例。
您可以在每个线程中使用更多。它不是一个存储。