2017-09-12 7 views
0

Pythonでコンビニエンス辞書を実装しようとしています - 具体的には、辞書は2つのスレッド、つまりclearupdateメソッドを使用するスレッドと、その値に直接アクセスします(つまり、__getitem__メソッドによって)。実装は以下の通りです:リーダとライタ間の安全性を確保した辞書

from threading import Lock, current_thread 

class ThreadSafeDict(dict): 
    def __init__(self, *args, **kwargs): 
     self._lock = Lock() 
     super(ThreadSafeDict, self).__init__(*args, **kwargs) 

    def clear(self, *args, **kwargs): 
     print("thread {} acquiring clear lock".format(current_thread().ident)) 
     self._lock.acquire() 
     print("thread {} acquired clear lock".format(current_thread().ident))   
     super(ThreadSafeDict, self).clear(*args, **kwargs) 
     print("thread {} releasing clear lock".format(current_thread().ident))   
     self._lock.release() 
     print("thread {} released clear lock".format(current_thread().ident))     

    def __getitem__(self, *args, **kwargs): 
     print("thread {} acquiring getitem lock".format(current_thread().ident)) 
     self._lock.acquire() 
     print("thread {} acquired getitem lock".format(current_thread().ident)) 
     val = super(ThreadSafeDict, self).__getitem__(*args, **kwargs) 
     print("thread {} releasing getitem lock".format(current_thread().ident))   
     self._lock.release() 
     print("thread {} released getitem lock".format(current_thread().ident)) 
     return val 

    def update(self, *args, **kwargs): 
     print("thread {} acquiring update lock".format(current_thread().ident))   
     self._lock.acquire() 
     print("thread {} acquiring update lock".format(current_thread().ident))   
     super(ThreadSafeDict, self).update(*args, **kwargs) 
     print("thread {} releasing update lock".format(current_thread().ident))   
     self._lock.release() 
     print("thread {} released update lock".format(current_thread().ident)) 

私は、このスクリプトで実装をテストしています:

import threading 
import random 
import time 

from threadsafedict import ThreadSafeDict 


def reader(tsd): 
    while True: 
     try: 
      val = tsd[1] 
     except KeyError: 
      pass 
     interval = random.random()/2 
     time.sleep(interval) 

def writer(tsd): 
    while True: 
     tsd.clear() 
     interval = random.random()/2 
     time.sleep(interval) 
     tsd.update({1: 'success'}) 

def main(): 
    tsd = ThreadSafeDict() 
    w_worker = threading.Thread(target=writer, args=(tsd,)) 
    r_worker = threading.Thread(target=reader, args=(tsd,)) 
    w_worker.start() 
    r_worker.start() 
    w_worker.join() 
    r_worker.join() 


if __name__ == '__main__': 
    main() 

出力例:

thread 140536098629376 acquiring clear lock 
thread 140536098629376 acquired clear lock 
thread 140536098629376 releasing clear lock 
thread 140536098629376 released clear lock 
thread 140536090236672 acquiring getitem lock 
thread 140536090236672 acquired getitem lock 
thread 140536090236672 acquiring getitem lock 
thread 140536098629376 acquiring update lock 

私が間違って何をしているのですか?

(私はこの同時実行が既にCPythonで安全だろう実現が、私は、実装に依存しないようにしようとしている)

答えて

1

問題は、あなたのThreadSafeDict.__getitem()__方法でsuper().__getitem__()呼び出しが持つアイテムを見つけるために失敗した場合ということです指定されたキーを入力すると、KeyErrorが発生し、__getitem__()メソッドの残りの部分がスキップされます。これは、ロックが解放されず、その後のいずれかのメソッドへの呼び出しが、決してロックが解除されないロックを取得するのを待ってブロックされることを意味します。

「取得されたgetitemロック」メッセージの後に「解放」メッセージと「解放された」メッセージがないことがわかります。これは、読取りスレッドによってロックを取得する別の試み。テストコードでは、読み取りスレッドは、clear()が実行された後、書き込みスレッドによってupdate()が実行されるまでの間に実行されると、常にこの条件になります。

修正するには、__getitem__()メソッドでKeyError例外をキャッチし、ロックを解除して例外を再発行します。 'try/finally'の構文は、これを行うための非常に簡単な方法を提供します。実際には、これは 'finally'の使用のための完璧な状況です。

または、ロックを取得してからsuper().__getitem__()を呼び出す前に、目的のキーが存在するかどうかを確認できますが、キーが存在すると予想される場合は少しパフォーマンスが低下します。

ところで、ThreadSafeDictdictクラスから継承させることはお勧めできません。これにより、はすべてdictのメソッド(たとえば、__setitem__())を継承し、オーバーライドしていないメソッドは、誰かが使用した場合にロックをバイパスします。これらのメソッドをすべてオーバーライドする準備ができていない場合は、基礎となるdictをクラスのインスタンスメンバーにする方が安全です。

関連する問題