Pythonでスレッドローカルストレージを使用するにはどうすればよいですか?
質問で述べたように、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
モジュールはスレッド(プロセスのグローバルデータにアクセスできる)の通常の概念を使用しますが、これらはグローバルインタープリターロックのためあまり有用ではないことに注意してください。異なる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
_
簡単に見落とされがちな1つの重要なこと:threading.local()
オブジェクトは、スレッドごとまたは関数呼び出しごとに1回ではなく、1回だけ作成する必要があります。 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 __== '__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
属性がクラスメンバーである(したがって、スレッド間で共有される)場合でも、各スレッドが独自のカウンターを保持する方法に注意してください。
同じ例では、インスタンス変数またはローカル変数を使用することもできますが、共有は行われないため、あまり表示されません(dictも同様に機能します)。インスタンス変数またはローカル変数としてスレッドローカルストレージが必要な場合もありますが、それらは比較的まれである(そしてかなり微妙な)傾向があります。
書くこともできます
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などの利用可能なデフォルト値を確認できます。