如何在 Python 中使用线程本地存储?
有关的
- 什么是 Python 中的“线程本地存储”,我为什么需要它?- 该线程似乎更关注何时共享变量。
- 在 Python 中确定特定函数是否在堆栈中的有效方法- Alex Martelli 给出了一个很好的解决方案
如何在 Python 中使用线程本地存储?
线程本地存储很有用,例如,如果您有一个线程工作池并且每个线程都需要访问自己的资源,例如网络或数据库连接。请注意,该threading
模块使用线程的常规概念(可以访问进程全局数据),但由于全局解释器锁,这些并不太有用。不同的multiprocessing
模块为每个模块创建一个新的子进程,因此任何全局都将是线程本地的。
这是一个简单的例子:
import threading
from threading import current_thread
threadLocal = threading.local()
def hi():
initialized = getattr(threadLocal, 'initialized', None)
if initialized is None:
print("Nice to meet you", current_thread().name)
threadLocal.initialized = True
else:
print("Welcome back", current_thread().name)
hi(); hi()
这将打印出:
Nice to meet you MainThread
Welcome back MainThread
一件容易被忽视的重要事情:一个threading.local()
对象只需要创建一次,而不是每个线程一次,也不是每个函数调用一次。global
或class
级别是理想的位置。
原因如下:threading.local()
实际上每次调用时都会创建一个新实例(就像任何工厂或类调用一样),因此threading.local()
多次调用会不断覆盖原始对象,这很可能不是人们想要的。当任何线程访问现有threadLocal
变量(或任何它被调用的变量)时,它都会获得该变量自己的私有视图。
这不会按预期工作:
import threading
from threading import current_thread
def wont_work():
threadLocal = threading.local() #oops, this creates a new dict each time!
initialized = getattr(threadLocal, 'initialized', None)
if initialized is None:
print("First time for", current_thread().name)
threadLocal.initialized = True
else:
print("Welcome back", current_thread().name)
wont_work(); wont_work()
将导致此输出:
First time for MainThread
First time for MainThread
所有全局变量都是线程本地的,因为multiprocessing
模块为每个线程创建一个新进程。
考虑这个例子,其中processed
计数器是线程本地存储的一个例子:
from multiprocessing import Pool
from random import random
from time import sleep
import os
processed=0
def f(x):
sleep(random())
global processed
processed += 1
print("Processed by %s: %s" % (os.getpid(), processed))
return x*x
if __name__ == '__main__':
pool = Pool(processes=4)
print(pool.map(f, range(10)))
它将输出如下内容:
Processed by 7636: 1
Processed by 9144: 1
Processed by 5252: 1
Processed by 7636: 2
Processed by 6248: 1
Processed by 5252: 2
Processed by 6248: 2
Processed by 9144: 2
Processed by 7636: 3
Processed by 5252: 3
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
...当然,线程 ID 和每个和顺序的计数会因运行而异。
线程本地存储可以简单地被认为是一个命名空间(通过属性表示法访问值)。不同之处在于每个线程透明地获取自己的一组属性/值,因此一个线程看不到另一个线程的值。
就像普通对象一样,您可以threading.local
在代码中创建多个实例。它们可以是局部变量、类或实例成员或全局变量。每一个都是一个单独的命名空间。
这是一个简单的例子:
import threading
class Worker(threading.Thread):
ns = threading.local()
def run(self):
self.ns.val = 0
for i in range(5):
self.ns.val += 1
print("Thread:", self.name, "value:", self.ns.val)
w1 = Worker()
w2 = Worker()
w1.start()
w2.start()
w1.join()
w2.join()
输出:
Thread: Thread-1 value: 1
Thread: Thread-2 value: 1
Thread: Thread-1 value: 2
Thread: Thread-2 value: 2
Thread: Thread-1 value: 3
Thread: Thread-2 value: 3
Thread: Thread-1 value: 4
Thread: Thread-2 value: 4
Thread: Thread-1 value: 5
Thread: Thread-2 value: 5
请注意每个线程如何维护自己的计数器,即使该ns
属性是类成员(因此在线程之间共享)。
同一个示例可以使用实例变量或局部变量,但这不会显示太多,因为那时没有共享(字典也可以工作)。在某些情况下,您需要将线程局部存储作为实例变量或局部变量,但它们往往相对较少(而且非常微妙)。
如问题中所述,Alex Martelli 在此处给出了解决方案。这个函数允许我们使用工厂函数为每个线程生成一个默认值。
#Code originally posted by Alex Martelli
#Modified to use standard Python variable name conventions
import threading
threadlocal = threading.local()
def threadlocal_var(varname, factory, *args, **kwargs):
v = getattr(threadlocal, varname, None)
if v is None:
v = factory(*args, **kwargs)
setattr(threadlocal, varname, v)
return v
还可以写
import threading
mydata = threading.local()
mydata.x = 1
mydata.x 将只存在于当前线程中
我跨模块/文件进行线程本地存储的方式。以下内容已在 Python 3.5 中经过测试 -
import threading
from threading import current_thread
# fileA.py
def functionOne:
thread = Thread(target = fileB.functionTwo)
thread.start()
#fileB.py
def functionTwo():
currentThread = threading.current_thread()
dictionary = currentThread.__dict__
dictionary["localVar1"] = "store here" #Thread local Storage
fileC.function3()
#fileC.py
def function3():
currentThread = threading.current_thread()
dictionary = currentThread.__dict__
print (dictionary["localVar1"]) #Access thread local Storage
在 fileA中,我启动了一个线程,该线程在另一个模块/文件中具有目标函数。
在 fileB中,我在该线程中设置了一个我想要的局部变量。
在 fileC中,我访问当前线程的线程局部变量。
此外,只需打印 'dictionary' 变量,以便您可以看到可用的默认值,如 kwargs、args 等。