2015-10-29 11 views
9

私はDjangoサービスで動作している大規模なPythonアプリケーションを持っています。私は、このコンテキストマネージャを作成したように、特定の操作の許可検査をオフにする必要があります。doものの中で2.7 Pythonコンテキストマネージャをスレッドセーフにする方法

with override_tests: 
    do stuff 
    ... 

:アプリケーションの

class OverrideTests(object): 

    def __init__(self): 
     self.override = 0 

    def __enter__(self): 
     self.override += 1 

    # noinspection PyUnusedLocal 
    def __exit__(self, exc_type, exc_val, exc_tb): 
     self.override -= 1 
     assert not self.override < 0 

    @property 
    def overriding(self): 
     return self.override > 0 

override_tests = OverrideTests() 

様々な部分は、コンテキストマネージャを使用してテストをオーバライドすることができます上記のコンテキストマネージャは、異なる機能で複数回使用することができます。カウンタの使用はこれを制御し続け、スレッドが関与するまでうまくいくように見えます。

スレッドが関与すると、グローバルコンテキストマネージャが再利用され、その結果、テストが誤って上書きされる可能性があります。ここで

は、簡単なテストケースである - thread.start_new_thread(do_id,())ラインが簡単なdo_itに置き換えられますが示すように、見事に失敗した場合、これは正常に動作します:

def stat(k, expected): 
    x = '.' if override_tests.overriding == expected else '*' 
    sys.stdout.write('{0}{1}'.format(k, x)) 


def do_it_inner(): 
    with override_tests: 
     stat(2, True) 
    stat(3, True) # outer with context makes this true 


def do_it(): 
    with override_tests: 
     stat(1, True) 
     do_it_inner() 
    stat(4, False) 


def do_it_lots(ntimes=10): 
    for i in range(ntimes): 
     thread.start_new_thread(do_it,()) 

私はそれぞれになるように、このコンテキストマネージャスレッドが安全に行うことができますどのようにPythonスレッドは、リエントラントであっても一貫して使用されますか?

+2

コンテキストマネージャを1つ作成せずに、代わりに「OverrideTests()」を使用して、毎回別のインスタンスを作成しましたか? – BrenBarn

+0

テストコードは、テストを適用するかどうかを知るために、コンテキストマネージャシングルトンの「グローバル値」にアクセスします。これはwithステートメントの直後のコンテキストにはありません。テストメソッドに 'override_tests.overriding:return True'があればオーバーライドされます。複数のコンテキストマネージャインスタンスがある場合、私はそれを行うことができません。 –

+2

グローバルな状態の変化に依存しているものは、スレッドに関する問題に遭遇します。ここで構造全体を再考する必要があるかもしれないように思えます。その種のグローバルフラグをチェックすることによって行動を変える機能を持つことはあまり堅牢ではありません。たとえば、すべてのテスト関数をクラスに入れて、各インスタンスが独自のオーバーライドステータスを格納し、スレッドごとにそのクラスの新しいインスタンスを作成することができます。 – BrenBarn

答えて

5

と思われる方法です:OverrideTestsクラスをthreading.localのサブクラスにします。 (あなたがいない場合でも動作するようですが)安全のために、あなたは、あなたの__init__でスーパー__init__を呼び出す必要があります:

class OverrideTests(threading.local): 

    def __init__(self): 
     super(OverrideTests, self).__init__() 
     self.override = 0 

    # rest of class same as before 

override_tests = OverrideTests() 

その後:

>>> do_it_lots() 
1.1.1.2.2.1.1.1.1.1.1.3.3.2.2.2.2.2.2.4.4.3.1.3.3.3.3.4.3.2.4.4.2.4.3.4.4.4.3.4. 

しかし、私は入れないだろうあなたの実際のアプリケーションがあなたがここに示した例よりも複雑であるならば、これは何らかのコーナーケースで失敗しないでしょう。結局のところ、あなたは本当にあなたのデザインを考え直すべきです。あなたの質問では、「コンテキストマネージャーをスレッドセーフにする」方法に焦点を当てています。しかし実際の問題はコンテキストマネージャだけでなく、ファンクション(例ではstat)です。 statは、グローバルに状態(グローバルoverride_tests)に依存していますが、これは本質的にスレッド環境では脆弱です。

+0

OK - これまでのソリューションが動作しているようです。私はQAサーバーに変更を提出しており、今後24時間以内に再調査がなければこの回答を承認します。 –

+0

いくつかのグローバルな状態に依存する必要があり、そのような状態を機能的なパラメータ化を通して完全に渡すことは実用的でない場合があります。私の実際の世界の例は、実際にはこの例のようにシンプルに保たれ、うまくいっているようです。 –

+0

これはしばらくの間実行されており、問題を完全に解決したようです。 –

-1

threading.RLockは同じスレッドで何度も取得できるリエントラントロックです。また、コンテキスト管理プロトコルもサポートしているため、withステートメントで使用できます。

現在、ロックを保持しているスレッドを表すownerフィールドがあります。プライベートメソッド_is_ownedは、呼び出しスレッドがロックを所有しているかどうかを示します。所有者の値は、現在のスレッドによってロックが保持されているかどうかを判断するために使用され、実装が簡単になります

カウンタはなく、スレッドローカルストレージは必要ありません。現在のスレッドが所有者でない場合、現在のスレッドはロックをホールドしないので、オーバーライドしていないことを意味します。

import sys 
from threading import RLock 
try: 
    import _thread as thread 
except ImportError: 
    import thread 

from time import sleep 

class OverrideTests(type(RLock())): 

    @property 
    def overriding(self): 
     return self._is_owned() 

override_tests = OverrideTests() 

def stat(k, expected): 
    x = '.' if override_tests.overriding == expected else '*' 
    sys.stdout.write('{0}{1}'.format(k, x)) 
    sys.stdout.flush() 

def do_it_inner(): 
    with override_tests: 
     stat(2, True) 
    stat(3, True) # outer with context makes this true                           


def do_it(): 
    with override_tests: 
     stat(1, True) 
     do_it_inner() 
    stat(4, False) 


def do_it_lots(ntimes=10): 
    for _ in range(ntimes): 
     thread.start_new_thread(do_it,()) 
     random_sleep() 

if __name__ == '__main__': 
    do_it_lots() 
    sleep(2) 
関連する問題