はじめに
単純に,with構文でスレッドセーフな処理を行いたいというのが動機です.threading.Lock
自体がwith構文をサポートしていますが,with構文の中でwith構文を使った場合,ブロックを抜けるのがどのタイミングかわからず,検証が難しいと思ったのが動機です.
また,任意のタイミングでスレッドのロックと解放を指定するサンプルが非常に少ないです.かろうじて見つかりはしましたが,そもそも2008年とあまりにも記事が古く,2.x系の話をしているのではと思い,実際に動くかどうか調査することにしました.
簡単なサンプル
サンプルではクラスにwith構文を実装し,内部でスレッドをロックしています.そして,ブロックから抜けたときにスレッドを解放するようにしています.
from threading import Lock
class Test:
_lock = Lock()
def __init__(self):
print("init")
def __enter__(self):
Test._lock.acquire()
print("inter lock")
return self
def __exit__(self, type, value, trace):
print("exit")
Test._lock.release()
def hello(self):
print("hello")
with Test() as t:
t.hello()
Test._lock
変数はstaticな変数です.従って,クラス間で共有されています.
例えば,このTest
クラスがWebアプリケーションのデータベース周りの処理だったとして,複数リクエストが発生して同時にinsert
すると例外を発生する,もしくはデータの整合性が取れなくなる,という仕様だった場合,スレッドをロックしないと非常に困ったことになります.
クラス利用者がスレッドセーフな処理を意識せずにコードを書ける,ということは地味に重要なことです.
with構文が走るとロックするようなクラスを作る
当然ですが,他にも同系統のクラスを作成することが考えられます.
でしたら,クラスを継承するだけで自動的にwith構文の中でスレッドをロック・解放する処理ができると便利ですね.
from threading import Lock
class TestInterface:
_lock = Lock()
def __init__(self):
print("init Interface")
def __enter__(self):
Test._lock.acquire()
print("inter lock")
return self
def __exit__(self, type, value, trace):
print("exit")
Test._lock.release()
def hello(self):
print("hello")
class Test(TestInterface):
def __init__(self):
super().__init__()
print("init Test")
with Test() as t:
t.hello()
この例では親クラスとしてTestInterface
を作り,Test
クラスで継承しています.これにより,with構文に入った段階で自動的にスレッドをロックするような仕組みを作れます.
注意点
子クラスで__enter__
や__exit__
を実装すると,親クラスの__enter__
や__exit__
が実行されません.子クラスで明示的に親クラスの__enter__
や__exit__
を呼び出してあげる必要があります.
子クラスでもwith構文を実装する場合は以下のようになります.
from threading import Lock
class TestInterface:
_lock = Lock()
def __init__(self):
print("init Interface")
def __enter__(self):
Test._lock.acquire()
print("interface lock")
return self
def __exit__(self, type, value, trace):
print("interface exit")
Test._lock.release()
def hello(self):
print("hello")
class Test(TestInterface):
def __init__(self):
super().__init__()
print("init Test")
def __enter__(self):
super().__enter__()
print("in test")
return self
def __exit__(self, type, value, trace):
super().__exit__(type, value, trace)
print("out test")
with Test() as t:
t.hello()
おわりに
with構文中にスレッドを自動的にロック・解放する仕組みについて説明しました.神クラスにthreading.Lock
を持たせるのも有りですが,あまり美しくはありません.もし神クラスに持たせてしまったなら,逐一ロックされてスレッドが円滑に処理できなくなります.
マルチスレッドで重要なのはロックする場所です.今回は局所的にwith構文を使ってロックする方法を説明しました.以上です.