6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

with構文の中でスレッドをロックしたい

Last updated at Posted at 2018-09-30

はじめに

単純に,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構文を使ってロックする方法を説明しました.以上です.

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?