0Pricing
Python Academy · レッスン

データの安全な共有

キューとロックを使用します

「データの安全な共有」はCoddyKit上の無料Python Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPython Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Python Academyコースには全4レッスンが含まれています。

競合状態の問題

複数のスレッドが同じデータを変更すると、処理が交互に実行されて結果が壊れることがあります。これが競合状態です。データを安全に共有するには、同期用のツールが必要です。

import threading

counter = 0

def bump():
    global counter
    for _ in range(100000):
        counter += 1

ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter (may be < 200000):', counter)

ロックによる解決

threading.Lockを使うと、一度に1つのスレッドだけがクリティカルセクションに入れるようになります。共有データを更新する処理をwithブロックで囲んで取得します。

import threading

counter = 0
lock = threading.Lock()

def bump():
    global counter
    for _ in range(100000):
        with lock:
            counter += 1

ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter:', counter)

acquireとrelease

ロックはacquire()とrelease()を使って手動で操作できますが、with形式のほうが安全です。エラーが発生した場合でもロックが解放されるためです。

import threading

lock = threading.Lock()
lock.acquire()
try:
    print('inside critical section')
finally:
    lock.release()
print('released')

スレッドセーフなキュー

queue.Queueクラスは最初からスレッドセーフです。スレッドは明示的なロックなしでputとgetを実行できるため、データの受け渡しにはこの方法が推奨されます。

import queue, threading

q = queue.Queue()

def producer():
    for i in range(3):
        q.put(i)

t = threading.Thread(target=producer)
t.start()
t.join()
while not q.empty():
    print('got', q.get())

プロデューサーとコンシューマー

これは代表的なパターンです。プロデューサーがキューに処理対象をputし、コンシューマーがgetして処理します。ロックはキューがすべて管理します。

import queue, threading

q = queue.Queue()
results = []

def consumer():
    while True:
        item = q.get()
        if item is None:
            break
        results.append(item * 10)
        q.task_done()

t = threading.Thread(target=consumer)
t.start()
for i in range(3):
    q.put(i)
q.put(None)
t.join()
print(results)

joinとtask_done

q.join()は、putされたすべての要素に対応するtask_done()が呼び出されるまでブロックします。これにより、メインスレッドはすべての処理が完了するまで待機できます。

import queue, threading

q = queue.Queue()

def worker():
    while True:
        item = q.get()
        q.task_done()

threading.Thread(target=worker, daemon=True).start()
for i in range(5):
    q.put(i)
q.join()
print('all items processed')

再入可能性のためのRLock

通常のLockでは、同じスレッドが2回取得しようとするとデッドロックします。threading.RLock(再入可能ロック)なら取得回数を数えるため、そのような操作が可能です。

import threading

rlock = threading.RLock()

def outer():
    with rlock:
        inner()

def inner():
    with rlock:
        print('re-entered safely')

outer()

通知のためのEvent

threading.Eventを使うと、あるスレッドから別のスレッドへ通知できます。スレッドは、別のスレッドがset()を呼び出すまでwait()で待機します。

import threading

start = threading.Event()

def worker():
    start.wait()
    print('go!')

t = threading.Thread(target=worker)
t.start()
print('setting event')
start.set()
t.join()

プロセス間での共有

プロセス間では、代わりにmultiprocessing.Queueを使用します。これは別々のメモリ空間の間でオブジェクトをシリアライズします。

import multiprocessing as mp

def producer(q):
    for i in range(3):
        q.put(i * i)

if __name__ == '__main__':
    q = mp.Queue()
    p = mp.Process(target=producer, args=(q,))
    p.start()
    p.join()
    while not q.empty():
        print('received', q.get())

デッドロックの回避

デッドロックは、スレッドが互いのロックをいつまでも待ち続けることで発生します。複数のロックを取得する場合は、どこでも必ず同じ順序にすることで回避できます。

import threading

lock_a = threading.Lock()
lock_b = threading.Lock()

def safe():
    with lock_a:
        with lock_b:
            print('acquired in consistent order')

safe()

ロックよりキューを優先する

可能であれば、ロックで保護した共有可変状態よりも、キューを使ったメッセージパッシングを優先してください。そのほうが動作を理解しやすく、バグもはるかに少なくなります。

import queue

q = queue.Queue()
for word in ['safe', 'simple', 'clear']:
    q.put(word)
while not q.empty():
    print(q.get())

確認問題

安全なデータ共有の理解度を確認しましょう。

復習

データを安全に共有する方法を学びました。

  • 保護されていない共有状態は、競合状態によって壊れる可能性があります。
  • LockとRLockはクリティカルセクションを保護します。
  • queue.Queueはスレッドセーフであり、プロセス間ではmultiprocessing.Queueを使用します。
  • Eventはスレッド間で通知し、ロック取得順を統一するとデッドロックを回避できます。

次のコースでは、subprocessを使って外部プログラムを実行します。

よくある質問

「データの安全な共有」レッスンは無料ですか?

はい。「データの安全な共有」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Python Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Python Academyコースには全4レッスンが含まれています。

「データの安全な共有」で何を学びますか?

キューとロックを使用します ブラウザで直接実行するハンズオンコードでPython Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Python Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPython Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「データの安全な共有」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPython Academyレッスンでコードを書いて実行できますか?

はい。すべてのPython Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. スレッドとGIL
  2. ThreadPoolExecutor
  3. multiprocessingの基礎
  4. データの安全な共有
← Python Academyに戻る