在Python中线程化本地存储

Cas*_*ash 64 python multithreading thread-local-storage

如何在Python中使用线程本地存储?

有关

mbe*_*lls 105

例如,如果您有一个线程工作池,并且每个线程都需要访问自己的资源(如网络或数据库连接),则线程本地存储很有用.请注意,该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()
Run Code Online (Sandbox Code Playgroud)

这将打印出来:

Nice to meet you MainThread
Welcome back MainThread
Run Code Online (Sandbox Code Playgroud)

一个容易被忽视的重要事情:一个threading.local()对象只需要创建一次,而不是每个线程一次,也不需要每次函数调用一次.该级别globalclass级别是理想的位置.

原因如下: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()
Run Code Online (Sandbox Code Playgroud)

将导致此输出:

First time for MainThread
First time for MainThread
Run Code Online (Sandbox Code Playgroud)

多处理模块

所有全局变量都是线程本地的,因为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)))
Run Code Online (Sandbox Code Playgroud)

它将输出如下内容:

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]
Run Code Online (Sandbox Code Playgroud)

...当然,每个和订单的线程ID和计数因运行而异.

  • "请注意,线程模块使用常规的线程概念(可以访问进程全局数据),但由于全局解释器锁定,这些并不太有用."这是严重的吗?如果我正确地阅读这个,这是非常误导的,因为线程是非常有用和关键的,GIL与否. (8认同)
  • wont_work函数是错误的,但不是因为threading.local"必须在全局范围内使用".相反,代码使用局部变量(threading.local对象)并期望它在调用之间保留值.这不是局部变量的行为(你会得到与普通字典相同的问题). (2认同)
  • 您能以粗体显示吗:“一个容易被忽略的重要事项:一个threading.local()对象只需要创建一次,而不是每个线程一次,也不是每个函数调用一次” :)-我以为我疯了! (2认同)

Pau*_*ore 18

线程局部存储可以简单地被认为是命名空间(通过属性表示法访问值).不同之处在于每个线程透明地获取自己的一组属性/值,因此一个线程不会看到另一个线程的值.

就像普通对象一样,您可以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()
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

注意每个线程如何维护自己的计数器,即使该ns属性是类成员(因此在线程之间共享).

同一个例子可能使用了一个实例变量或一个局部变量,但这并没有显示太多,因为当时没有共享(一个dict也能正常工作).在某些情况下,您需要将线程局部存储作为实例变量或局部变量,但它们往往相对较少(而且非常微妙).


Cas*_*ash 16

正如问题所述,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
Run Code Online (Sandbox Code Playgroud)


Cas*_*ash 5

也可以写

import threading
mydata = threading.local()
mydata.x = 1
Run Code Online (Sandbox Code Playgroud)

mydata.x只存在于当前线程中

  • 而不是将这种代码放在自己的答案中,为什么不编辑你的问题呢? (4认同)
  • @Evan:因为有两种基本方法,它们实际上是单独的答案 (2认同)

Shi*_*gga 5

我跨模块/文件进行线程本地存储的方法。以下内容已在 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
Run Code Online (Sandbox Code Playgroud)

在 fileA 中,我启动一个线程,该线程在另一个模块/文件中具有目标函数。

在 fileB 中,我在该线程中设置了一个我想要的局部变量。

在 fileC 中,我访问当前线程的线程局部变量。

此外,只需打印“字典”变量,以便您可以看到可用的默认值,例如 kwargs、args 等