Python の threading.Lock を試してみる
複数のスレッドから同時に実行されると困るパート (クリティカルセクション) を保護するのに Lock を試してみる。 サンプルは複数のスレッドから共有されるオブジェクトで、ロックあり・なしで挙動の違いを確認する。
#!/usr/bin/env python # -*- coding: utf-8 -*- from threading import Thread, Lock import time # スレッドセーフでないオブジェクト class NoLockedSharedObject(object): # 複数のスレッドから並列に実行されうるメソッド def count(self): # 以下をクリティカルセクションに見立てる for i in range(10): print i, time.sleep(0.1) print # スレッドセーフなオブジェクト class LockedSharedObject(NoLockedSharedObject): def __init__(self): super(LockedSharedObject, self).__init__() # ロックオブジェクトを作っておく self.lock = Lock() # ロックによって同時に一つのスレッドからしか実行されないメソッド def count(self): # 別のスレッドがメソッドを実行中にスレッドがステートメントに突入するとブロックする with self.lock: # クリティカルセクションを実行する super(LockedSharedObject, self).count() # 共有オブジェクトを使うスレッドクラス class CountThread(Thread): def __init__(self, shared_object): super(CountThread, self).__init__() # 複数のスレッドで共有されるオブジェクトを受け取る self.shared_object = shared_object self.daemon = True def run(self): self.shared_object.count() if __name__ == '__main__': print 'nolock' # ロックなしの共有オブジェクトを使う場合 nolocked = NoLockedSharedObject() t1 = CountThread(nolocked) t2 = CountThread(nolocked) t1.start() t2.start() t1.join() t2.join() print 'lock' # ロックありの共有オブジェクトを使う場合 locked = LockedSharedObject() t3 = CountThread(locked) t4 = CountThread(locked) t3.start() t4.start() t3.join() t4.join()
nolock 0 0 1 1 22 33 44 55 66 77 8 8 9 9 lock 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
ロックなしの方は二つのスレッドから並列に実行されているのに比べて、ロックありの方は一つのスレッドからしか実行されていない。
今回使ったのはただの Lock だけど、同じスレッドからであれば何度ロックを取得してもブロックしない RLock というロックも同じパッケージにある。
$ python ...(省略)... >>> import threading >>> rlock = threading.RLock() >>> rlock.acquire() True >>> rlock.acquire() 1 >>> rlock.acquire() 1 >>> lock = threading.Lock() >>> lock.acquire() True >>> lock.acquire() ...(ブロックして戻ってこない)...
二回目から RLock の返り値が変わってるのは、既にロックを取得済みか否かを判別できるようにするため…?