什么是Python中的“线程本地存储”,为什么需要它?

时间:2008-09-19 19:53:19

标签: python multithreading thread-local

在Python中,具体如何在线程之间共享变量?

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

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

提前致谢!

5 个答案:

答案 0 :(得分:70)

在Python中,除了函数局部变量之外,所有内容都是共享的(因为每个函数调用都有自己的本地集合,而线程总是单独的函数调用。)即便如此,只有变量本身(引用的名称)到对象)是函数的本地;对象本身总是全局的,任何东西都可以引用它们。 在这方面,特定线程的Thread对象不是特殊对象。如果将Thread对象存储在所有线程都可以访问的地方(如全局变量),则所有线程都可以访问该Thread个对象。如果你想原子地修改你不仅仅在同一个线程中创建的任何,并且没有存储另一个线程可以获得它的任何地方,你必须通过锁来保护它。并且所有线程当然必须共享这个锁,否则它将不会非常有效。

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

答案 1 :(得分:63)

请考虑以下代码:

#/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()
I'm called from Thread-2
I'm called from 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()
I'm called from Thread-2
I'm called from 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())

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

答案 2 :(得分:16)

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

我在这里可能是错的。如果您另外知道,请说明,因为这将有助于解释为什么需要使用线程local()。

该语句似乎正确无误:“如果您想自动修改另一个线程可以访问的任何内容,则必须使用锁来保护它。”我认为这句话->有效地<-正确,但并不完全正确。我认为“原子”一词意味着Python解释器创建了一个字节代码块,该块没有留给CPU中断信号的空间。

我认为原子操作是不提供对中断访问权限的Python字节代码块。诸如“ running = True”之类的Python语句是原子的。在这种情况下,您不需要从中断中锁定CPU(我相信)。 Python字节码故障可以防止线程中断。

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

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