2017-09-29 10 views
1

dictionary.get()関数を使用すると、辞書全体がロックされていますか?私はマルチプロセスとマルチスレッドプログラムを開発しています。この辞書は、データを追跡するための状態テーブルとして機能するために使用されます。私は辞書にサイズ制限を課さなければならないので、制限が当たったときはいつでも、タイムスタンプに基づいてテーブル上でガベージコレクションを行う必要があります。現在の実装では、ガベージコレクションがテーブル全体を反復しながら、オペレーションの追加が遅れることになります。Python dict.get()Lock

私は2つ以上のスレッドを持っています.1つはデータを追加するだけで、もう1つはガベージコレクションを行います。パフォーマンスは重要です私のプログラムでストリーミングデータを処理する。私のプログラムはストリーミングデータを受信して​​おり、メッセージを受信するたびに状態テーブルでそれを探し、最初に存在しないレコードを追加するか、特定の情報をコピーしてパイプに沿って送信しなければなりません。

私はmultiprocessingを使用して検索と追加操作を同時に行うことを考えましたが、プロセスを使用すると、各プロセスに状態テーブルのコピーを作成する必要があります。その場合、同期のパフォーマンスオーバーヘッドが高すぎます。そして私はまた、multiprocessing.manager.dict()が各CRUD操作のためのアクセスをロックしていることを読んでいます。私は現在のアプローチでスレッディングを使用しているので、オーバーヘッドを犠牲にすることはできませんでした。

私の質問は、1つのスレッドがテーブル上で.get()del dict['key']の操作を実行している間に、他の挿入スレッドにアクセスできないようにしますか?

注:私が最もSOのPythonの辞書関連の記事を読んでいるが、私は答えを見つけるように見えることはできません。ほとんどの人は、たとえPython辞書操作がアトミックであっても、ロックを挿入/更新する方が安全だと答えています。私はストリーミングデータの膨大な量を処理しているのでロックする毎回私には適していません。より良いアプローチがあるかどうかアドバイスしてください。

+0

これはあなたの主な質問には対応していませんので、私はコメントしています。競合状態が心配しているときはいつでも、ワンステップ操作を使いたいと思う。 2段階の 'get'から' del'はお勧めできません - 代わりに1段階 'pop'を使います。私はワンステップアプローチでさえあなたの場合に特別な努力をすることなく原子になるとは思わないので、ここでは「原子」という言葉を避けました。 –

+0

コメントありがとうございます。私の場合は、キーが辞書の値の中のすべてのハッシュ値であるように、文法を設計しました。それは実行時に生成され、私はそれらを追跡していません。 popはitemをpopするために* key *を使う必要があります。私は今のところそれを使うことはできないと思う。しかし、2の操作を1に変更することは良い考えです。 –

+0

キーなしで 'get'をどうやって使いますか? –

答えて

3

ハッシュのプロセスや辞書でキーを比較するには、任意のPythonコードを呼び出すことができる場合(キーが内蔵されたCで実装タイプのすべてのPythonでない場合は、基本的には、例えばstrintfloat、など) 、そうであれば、バケットの衝突が解決されている間(均等性テスト中)にGILが解放され、別のスレッドが飛び込んで比較対象が消滅する競合状態が発生する可能性がありますdict。彼らは実際にインタプリタをクラッシュさせないようにしようとしますが、それは過去にエラーの原因となっていました。

可能性がある場合(またはCPython以外のインタプリタの場合、GILがこのような基本的な保証を提供していない場合)、本当にロックを使用してアクセスを調整する必要があります。 CPythonでは、現代のPython 3を使用している限り、コストはかなり低くなります。 GILは実際に一度に1つのスレッドのみが実行されることを保証するので、ロックに対する競合はかなり低くなるはずです。あなたのロックは(競合がGIL上にあるため)ロックされていないはずですので、それを使用するための増分コストはかなり小さくすべきです。

注:collections.OrderedDictを使用して、テーブルのサイズを制限するプロセスを簡素化することを検討することもできます。

with lock: 
    try: 
     try: 
      odict.move_to_end(key) # If key already existed, make sure it's "renewed" 
     finally: 
      odict[key] = value # set new value whether or not key already existed 
    except KeyError: 
     # move_to_end raising key error means newly added key, so we might 
     # have grown larger than limit 
     if len(odict) > maxsize: 
      odict.popitem(False) # Pops oldest item 

と使用方法として行わ:

with lock: 
    # move_to_end optional; if using key means it should live longer, then do it 
    # if only setting key should refresh it, omit move_to_end 
    odict.move_to_end(key) 
    return odict[key] 

これが行うOrderedDictを使用すると、のように行わテーブルへの追加を行うことで、厳密なLRU(最低使用頻度)システムなどのサイズ制限を実装することができますロックを必要としますが、 "すべてのキーを確認"(O(n)作業)から "何も見ずに最も古いアイテムをポップする"(O(1)作業)から大きくなりすぎると、ガベージコレクションの作業量が減ります。

+0

私のキーはすべて文字列型です。だから私は競争条件に気を付ける必要はないということですか?最も古いアイテムをポップするあなたのアプローチはとても良いアイデアです。それはプログラムに多くの時間を節約することができます。 –

+0

@ThuYeinTun:すべての 'str'キーで、CPythonリファレンスインタプリタを使用している場合、単一項目に影響を与えるほとんどの個々の関数呼び出しと、最も基本的なアトミックアクションは、原子的に動作しますが、依然として制限されます。 'mydict [key] + = 1'のようなものが本当に複数のステップであり、スレッド間で矛盾してインクリメントを落としてしまうようなものであっても、2つのことをする必要がある場合は、' dict'多段階操作がアトミックに動作するようにロックする必要があります。 – ShadowRanger

0

ロックは競合状態を回避するために使用されるため、2つのスレッドが同時にディクテーションを変更することはできません。そのため、ロックを使用することをお勧めします。競合状態になり、プログラムが失敗する可能性があります。ミューテックスロックを使用して2つのスレッドを処理できます。

関連する問題