Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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

GRGSIBERIA
なんでもやる人.元未踏クリエータ.三次元幾何学と音響工学を少々.
http://www.grgsiberia.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした