1. ホーム
  2. python

[解決済み] Pythonのスレッドローカルストレージ

2023-06-02 19:53:27

質問

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やそれぞれのカウント、順番は実行ごとに異なります。