LoginSignup
5
7

More than 3 years have passed since last update.

スレッド間排他制御のwithステートメント考察

Posted at

はじめに

pythonでスレッド間排他制御の仕組みが知りたくて検索。

tlock = threading.Lock()
tlock.acquire()
    ....
    ....
tlock.release()

まず、上記がWebでヒットしました。

これじゃ~

Windows C++で言うところのクリティカルセクション、CSingleLockクラス的な同期機構があります。
またjavaにも排他制御機構としてsynchronizedがあります。
C++でCSingleLockで排他制御をすると自動変数上に定義すれば関数を抜ける時、自動的に破棄され
アンロックし忘れを無くすことができます。
javaでもsynchronizedで排他制御でき、そのブロックを抜ければアンロックされます。
アンロックし忘れがありません。
さらにpythonのエラーや例外がtry-exceptで処理されるので、これが開始/終了処理があるものは
処理を複雑化します。
C++やjavaのような似たようなものは無いの?と思い調べました。

withステートメント排他

次にwithステートメントを使った排他制御はWebでヒットしました。

tlock = threading.Lock()
    with tlock:
        ....
        ....

でも、これってどう動く?
そこでサンプルを作って試してみました。

サンプル

thread1.py
import time
import threading

LOOP_CNT = 5
threadlock = threading.RLock()
#threadlock = threading.Lock()

# スレッド1 再帰構造
def threadfunc1(rid):
    if 2 < rid:
        return
    tname = threading.currentThread().getName()
    for cnt in range(0, LOOP_CNT):
        with threadlock:
            print("threadfunc1 rid:%d %d %s" % (rid, cnt, tname))
            if cnt == 1:
                threadfunc1(rid+1)
        time.sleep(0.1)
    print("threadfunc1() exit rid=%d" % rid)

# スレッド2
def threadfunc2(rid):
    tname = threading.currentThread().getName()
    for cnt in range(0, LOOP_CNT):
        with threadlock:
            if cnt == 3:
                print("threadfunc2 locking... rid:%d %d %s" % (rid, cnt, tname))
                time.sleep(1)
                print("threadfunc2 unlocked.. rid:%d %d %s" % (rid, cnt, tname))
            else:
                print("threadfunc2 rid:%d %d %s" % (rid, cnt, tname))
        time.sleep(0.1)
    print("threadfunc2() exit rid=%d" % rid)

def main():
    thread1 = threading.Thread(target=threadfunc1, args=(0,))
    thread2 = threading.Thread(target=threadfunc2, args=(0,))
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

if __name__ == "__main__":
    main()

thread1.pyをthreading.RLockを使用し、thread2.pyはLockを使って排他制御を行います。
上記ソースコードコメント付け替えて頂ければ良いかと思います。
threadfunc1()関数は、cntループカウンタが1の時、再帰呼び出しを行います。
threadfunc2()関数は、cntループカウンタが3の時に意図的に長い1秒ほどのsleep()を入れています。
これでうまく排他制御されるかを試します。

環境

Anaconda Python 3.6.9

RLock排他制御の結果

排他制御機能を持つRLockを使った結果です。
排他制御が機能しており、スムーズに流れています。
threadfunc1()で再帰呼び出し中は、ちゃんとthreadfunc2()をブロックしております。
threadfunc2()で長い時間ロックしていても、threadfunc1()側は待機していますね。

$ python thread1.py
threadfunc1 rid:0 0 Thread-1
threadfunc2 rid:0 0 Thread-2
threadfunc2 rid:0 1 Thread-2
threadfunc1 rid:0 1 Thread-1
threadfunc1 rid:1 0 Thread-1
threadfunc1 rid:1 1 Thread-1
threadfunc1 rid:2 0 Thread-1
threadfunc1 rid:2 1 Thread-1
threadfunc1 rid:2 2 Thread-1
threadfunc1 rid:2 3 Thread-1
threadfunc1 rid:2 4 Thread-1
threadfunc1() exit rid=2
threadfunc1 rid:1 2 Thread-1
threadfunc1 rid:1 3 Thread-1
threadfunc1 rid:1 4 Thread-1
threadfunc1() exit rid=1
threadfunc2 rid:0 2 Thread-2
threadfunc1 rid:0 2 Thread-1
threadfunc2 locking... rid:0 3 Thread-2
threadfunc2 unlocked.. rid:0 3 Thread-2
threadfunc1 rid:0 3 Thread-1
threadfunc2 rid:0 4 Thread-2
threadfunc1 rid:0 4 Thread-1
threadfunc1() exit rid=0
threadfunc2() exit rid=0

Lock排他制御

Lockは再帰構造未対応なので、途中で止まってしまいました。

$ python thread2.py
threadfunc1 rid:0 0 Thread-1
threadfunc2 rid:0 0 Thread-2
threadfunc2 rid:0 1 Thread-2
threadfunc1 rid:0 1 Thread-1

・・・・ ロック状態 ・・・・

いい感じで動きました。当たり前と言えば当たり前の結果なのですが実際動かしてみないと、
ピンとこないというか腑に落ちないところがあり試してみました。
他にも排他制御ファンクションはあるかと思いますが、今回はここまでということで。

参考

synchronized のメモ
with構文の中でスレッドをロックしたい
pythonのthreadで排他制御
スレッドベースの並列処理

5
7
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
5
7