Python 3でのスレッディングの基本
Python では threading モジュールを使用してスレッドを管理します。スレッディングを使用すると、複数のタスクを並行して実行できるため、プログラムの効率が向上する可能性があります。しかし、複数のスレッドが同時に同じデータにアクセスするとデータの不整合が起こる可能性があるため、適切な同期処理が必要です。
スレッドの作成と実行
まずは基本的なスレッドの作成と実行の方法について見ていきましょう。以下のサンプルコードは、スレッドを作成し、それぞれが異なるタスクを実行する簡単な例です。
import threading
import time
def print_numbers():
for i in range(5):
time.sleep(1)
print(f"Number: {i}")
def print_letters():
for letter in 'abcde':
time.sleep(1.5)
print(f"Letter: {letter}")
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
このコードを実行すると、2つのスレッドが同時に動作し、「Number」と「Letter」が交互に出力されます。出力の順序は実行するたびに異なる場合があります。
スレッドの同期
次に、スレッド間でのデータの整合性を保つための同期の方法について説明します。共有データへのアクセスを制御するためにロックを使用する例を見てみましょう。
import threading
counter = 0
lock = threading.Lock()
def update_counter():
global counter
with lock:
current = counter
print(f"Current: {current}")
counter = current + 1
threads = [threading.Thread(target=update_counter) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(f"Final counter: {counter}")
このコードでは、lock オブジェクトを使用して、カウンターの更新を一度に1スレッドだけが行うように制限しています。これにより、カウンターの値が正しく10まで増加します。
スレッドのデッドロック
複数のロックを使用する場合、スレッドがデッドロックに陥る可能性があります。デッドロックを避けるための一般的なテクニックを以下のコードで示します。
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1_process():
with lock1:
print("Thread 1 acquired lock1")
with lock2:
print("Thread 1 acquired lock2")
def thread2_process():
with lock2:
print("Thread 2 acquired lock2")
with lock1:
print("Thread 2 acquired lock1")
t1 = threading.Thread(target=thread1_process)
t2 = threading.Thread(target=thread2_process)
t1.start()
t2.start()
t1.join()
t2.join()
この例では、二つのスレッドが異なる順序でロックを取得しようとしています。これを回避するためには、すべてのスレッドがロックを同じ順序で取得するように設計する必要があります。
以上のように、Pythonのスレッディングは非常に強力ですが、データの整合性を保つためには注意深い設計が求められます。適切な同期処理を行うことで、バグやデータの不整合を防ぎながら、プログラムのパフォーマンスを向上させることができます。
