0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

concurrent.futuresでLockの使い方


Lockとは?

  • Lock は、複数のスレッドやプロセスが同時に共有変数やリソースにアクセスする際に、そのアクセスを制御するための仕組みです。
  • Lockを使うことで、データの整合性 を守ることができます。これにより、複数のスレッドが同時に変数を変更してしまう「競合状態(race condition)」を防ぐことができます。

例題 1: import threading を使った Lock の利用

まず、import threading を使って Lock を導入する例を見てみましょう。

コード: import threading を使う例

from threading import Thread, Lock
import time

database_value = 0

def increase(lock):
    global database_value

    with lock :
        local_copy = database_value
        # processing
        local_copy += 1
        time.sleep(0.1)
        database_value = local_copy



# スクリプトを直接実行する場合のみメイン関数を呼び出す
if __name__ == "__main__":

    lock = Lock()
    print("start value", database_value)
    thread_1 = Thread(target=increase, args=(lock,))
    thread_2 = Thread(target=increase, args=(lock,))

    thread_1.start()
    thread_2.start()

    thread_1.join()
    thread_2.join()

    print("end value", database_value)

解説

  • Lockの目的: 上記のコードでは、複数のスレッドが counter という共有変数を同時に更新します。Lockを使うことで、1つのスレッドが変数の変更を完了するまで、他のスレッドのアクセスを一時的に停止します。これにより、データの不整合が防止されます。

  • 使い方: with lock という構文で、Lockを使ったブロックを定義し、そのブロック内での処理が終わるまで他のスレッドが変数にアクセスできないようにしています。

例題 2: concurrent.futures を使った Lock の利用

次に、from concurrent.futures を使って、同じように Lock を導入する例を見てみましょう。

コード: concurrent.futures を使う例

from concurrent.futures import ThreadPoolExecutor
import threading
import time

database_value = 0
lock = threading.Lock()


def increase():
    global database_value

    with lock:
        local_copy = database_value
        # processing
        local_copy += 1
        time.sleep(0.1)
        database_value = local_copy


if __name__ == "__main__":
    print("start value", database_value)

    # ThreadPoolExecutorを使用して2つのスレッドを実行
    with ThreadPoolExecutor(max_workers=2) as executor:
        # 2つのタスクを同時に実行
        futures = [
            executor.submit(increase)
            for _ in range(2)
        ]

        # すべての結果を待つ
        for future in futures:
            future.result()

    print("end value", database_value)

解説

  • このコードでは、ThreadPoolExecutor を使って、最大4つのスレッドで increment() 関数を並行に実行します。
  • Lockの使い方は、import threading を使った場合と同様です。with lock を使うことで、スレッドごとの変数更新が保護されます。
  • concurrent.futures を使う理由: これを使うと、スレッドの管理がよりシンプルになり、submit() メソッドで関数を並行に実行できます。これにより、スレッドの作成と管理が統一された構造で行えます。

Lockが必要な理由

  • データ競合の防止: Lockを使うことで、複数のスレッドが同時に同じ変数にアクセスし、データを不整合にすることを防げます。これにより、プログラムの動作が正確になります。
  • 例外的な状況の回避: Lockを使わない場合、変数の更新中に他のスレッドがアクセスする可能性があり、最終的な計算結果が正しくない場合があります。

まとめ

  • Lockは、並行処理においてデータの整合性を守るために必要です。
  • concurrent.futures でもLockを使うことができ、よりシンプルに並行処理を実装できます。
  • Lockを適切に使うことで、データの競合を防ぎ、プログラムが意図した通りに動作するようにできます。

参考資料

Threading in Python - Advanced Python 16 - Programming Tutorial
https://youtu.be/usyg5vbni34?si=3skpeFP_TZ_L1Yp1

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?