Pythonでスレッドローカルストレージを使用するにはどうすればよいですか?
関連している
- Pythonの「スレッドローカルストレージ」とは何ですか?なぜそれが必要なのですか?-このスレッドは、変数が共有されている場合に焦点を当てているようです。
- 特定の関数がPythonのスタックにあるかどうかを判断する効率的な方法-AlexMartelliが優れたソリューションを提供します
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
見落とされがちな重要なことの1つは、threading.local()
オブジェクトを作成する必要があるのは1回だけであり、スレッドごとに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 __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
属性がクラスメンバーである(したがってスレッド間で共有されている)場合でも、各スレッドが独自のカウンターを維持する方法に注意してください。
同じ例でインスタンス変数またはローカル変数を使用することもできますが、共有がないため、あまり表示されません(dictも同様に機能します)。インスタンス変数またはローカル変数としてスレッドローカルストレージが必要になる場合がありますが、それらは比較的まれである(そしてかなり微妙である)傾向があります。
質問で述べたように、AlexMartelliはここで解決策を示します。この関数を使用すると、ファクトリ関数を使用して各スレッドのデフォルト値を生成できます。
#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は現在のスレッドにのみ存在します
モジュール/ファイル間でスレッドローカルストレージを実行する私の方法。以下はPython3.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などの使用可能なデフォルト値を確認できます。