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

时间:2020-03-06 14:27:35  来源:igfitidea点击:

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

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

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

提前致谢!

解决方案

在Python中,所有内容都是共享的,除了函数局部变量(因为每个函数调用都有自己的局部变量集,并且线程始终是单独的函数调用。)即使这样,也只有变量本身(引用对象的名称)对于功能而言是本地的;对象本身始终是全局的,任何事物都可以引用它们。
在这方面,特定线程的"线程"对象不是特殊对象。如果将"线程"对象存储在所有线程都可以访问的位置(例如全局变量),那么所有线程都可以访问该"线程"对象。如果要原子地修改我们不只是在同一线程中创建的任何内容,并且不存储其他线程可以访问的任何内容,则必须使用锁来保护它。当然,所有线程都必须共享这个非常相同的锁,否则效果不是很好。

如果我们需要实际的线程本地存储,那就是使用threading.local的地方。threading.local的属性不在线程之间共享;每个线程仅看到其自身放置在其中的属性。如果我们对它的实现感到好奇,请在标准库的_threading_local.py中找到源代码。

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

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

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

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

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

考虑以下代码:

#/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())

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