Python中的线程局部存储
在Python中,我该如何使用线程本地存储?
相关内容
- 什么是Python中的“线程本地存储”,我为什么需要它? - 这个讨论似乎更关注于变量是如何共享的。
- 在Python中有效判断某个函数是否在栈上的方法 - Alex Martelli提供了一个不错的解决方案。
5 个回答
正如问题中提到的,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
线程本地存储可以简单理解为一个命名空间(通过属性的方式来访问值)。不同的是,每个线程都有自己的一套属性和对应的值,这样一个线程就看不到另一个线程的值。
就像普通对象一样,你可以在代码中创建多个 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
属性是一个类成员(因此在多个线程之间是共享的)。
同样的例子也可以使用实例变量或局部变量,但那样就没什么意义了,因为那样就没有共享(字典也可以很好地工作)。虽然在某些情况下,你可能需要将线程本地存储作为实例变量或局部变量,但这种情况相对较少(而且比较微妙)。
线程本地存储很有用,比如当你有一个线程工作池,每个线程需要访问自己的资源,比如网络连接或数据库连接。需要注意的是,threading
模块使用的是普通的线程概念(这些线程可以访问进程的全局数据),但由于全局解释器锁的存在,这些线程的使用效果并不好。而multiprocessing
模块则为每个线程创建一个新的子进程,这样任何全局变量就会变成线程本地的。
threading模块
这里有一个简单的例子:
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模块
由于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和每个线程的计数以及顺序在每次运行时都会有所不同。