Python のスレッドローカル変数

threading.local() は、スレッドごとに独立したデータを保持するための仕組みです。グローバル変数のように見えますが、各スレッドには自分専用の値が格納されます。

スレッドローカル変数とは

通常のグローバル変数は、すべてのスレッドで共有されます。しかし threading.local() を使うと、スレッドごとに別々の値を持てます。

import threading

# スレッドローカル変数を作成
local_data = threading.local()

def worker(name):
    local_data.name = name  # このスレッド専用の値
    print(f"設定: {local_data.name}")
    
    # 少し待ってから再度確認
    import time
    time.sleep(0.1)
    print(f"確認: {local_data.name}")  # 他のスレッドの影響を受けない

threads = [
    threading.Thread(target=worker, args=("Alice",)),
    threading.Thread(target=worker, args=("Bob",)),
    threading.Thread(target=worker, args=("Charlie",)),
]

for t in threads:
    t.start()
for t in threads:
    t.join()

各スレッドは自分の name を保持し、他のスレッドからは見えません。

グローバル変数との違い

グローバル変数

すべてのスレッドで共有される。同時アクセスで競合が発生する可能性。

スレッドローカル変数

スレッドごとに独立。他のスレッドからアクセスできない。

import threading
import time

# グローバル変数(共有される)
global_value = None

# スレッドローカル変数(独立)
local_value = threading.local()

def worker(n):
    global global_value
    global_value = n
    local_value.value = n
    
    time.sleep(0.1)
    
    print(f"Thread {n}: global={global_value}, local={local_value.value}")

threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()
# global_value は最後に設定した値で上書きされる
# local_value.value は各スレッドで独立した値を保持

実用例:データベース接続

スレッドごとにデータベース接続を管理する例です。

import threading
import sqlite3

# スレッドごとの接続を管理
connections = threading.local()

def get_connection():
    if not hasattr(connections, 'conn'):
        connections.conn = sqlite3.connect(':memory:')
        print(f"{threading.current_thread().name}: 新しい接続を作成")
    return connections.conn

def worker():
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT 1")
    print(f"{threading.current_thread().name}: クエリ実行完了")

threads = [threading.Thread(target=worker, name=f"Thread-{i}") for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

各スレッドが自分専用の接続を持つため、接続の競合を防げます。

実用例:リクエストコンテキスト

Web フレームワークでリクエストごとのコンテキストを管理する例です。

import threading

request_context = threading.local()

def set_user(user_id):
    request_context.user_id = user_id

def get_user():
    return getattr(request_context, 'user_id', None)

def process_request(user_id):
    set_user(user_id)
    print(f"{threading.current_thread().name}: ユーザー {get_user()} の処理中")
    # 処理...

threads = [
    threading.Thread(target=process_request, args=(f"user_{i}",))
    for i in range(3)
]
for t in threads:
    t.start()
for t in threads:
    t.join()

初期値を持つスレッドローカル

サブクラスを作って初期値を設定することもできます。

import threading

class LocalData(threading.local):
    def __init__(self):
        self.value = 0  # 各スレッドでの初期値

local_data = LocalData()

def worker(n):
    print(f"初期値: {local_data.value}")
    local_data.value = n
    print(f"設定後: {local_data.value}")

threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

注意点

スレッドローカル変数の値は、スレッドが終了すると失われます。

import threading

local = threading.local()

def worker():
    local.data = "スレッドのデータ"
    print(f"設定: {local.data}")

thread = threading.Thread(target=worker)
thread.start()
thread.join()

# メインスレッドからはアクセスできない
try:
    print(local.data)
except AttributeError:
    print("メインスレッドには data がない")

スレッドローカル変数は、スレッドごとに独立した状態を持つ必要がある場面で非常に便利です。