web-dev-qa-db-ja.com

python dictをマルチプロセッシングと同期する方法

Python 2.6とマルチスレッド用のマルチプロセッシングモジュールを使用しています。ここで、同期されたdictが必要です(実際に必要なアトミック操作は、値の+ =演算子のみです)。 。

Dictをmultiprocessing.sharedctypes.synchronized()呼び出しでラップする必要がありますか?それとも別の方法ですか?

26
Peter Smit

イントロ

アームチェアの提案はたくさんあり、実際の例はないようです。ここにリストされている答えはどれもマルチプロセッシングの使用を示唆しておらず、これはかなりがっかりし、気がかりです。 python愛好家として、組み込みライブラリをサポートする必要があります。並列処理と同期は決して簡単なことではありませんが、適切な設計で簡単にできると思います。これは、現代のマルチコアアーキテクチャであり、十分に強調することはできません!とはいえ、マルチプロセッシングライブラリはまだ初期段階にあり、かなりの落とし穴やバグがあり、機能的なプログラミングに向けられているため、満足にはほど遠いです(私は嫌いです) )。現在でも、サーバーの実行中に新しく作成されたオブジェクトを共有できないというマルチプロセッシングの厳しい制限のため、マルチプロセッシングよりも Pyro モジュール(以前のモジュール)の方が好きです。「レジスター」マネージャーオブジェクトのclass-methodは、マネージャー(またはそのサーバー)が起動する前にのみ実際にオブジェクトを登録します。十分なおしゃべり、より多くのコード:

Server.py

from multiprocessing.managers import SyncManager


class MyManager(SyncManager):
    pass


syncdict = {}
def get_dict():
    return syncdict

if __name__ == "__main__":
    MyManager.register("syncdict", get_dict)
    manager = MyManager(("127.0.0.1", 5000), authkey="password")
    manager.start()
    raw_input("Press any key to kill server".center(50, "-"))
    manager.shutdown()

上記のコード例では、Server.pyは、同期された共有オブジェクトを提供できるマルチプロセッシングのSyncManagerを利用しています。マルチプロセッシングライブラリは、登録された各オブジェクトの「呼び出し可能」を見つける方法に非常に敏感であるため、このコードはインタプリタで実行しても機能しません。 Server.pyを実行すると、複数のプロセスを使用するためにsyncdictディクショナリを共有するカスタマイズされたSyncManagerが起動し、同じマシン上で、またはループバック以外のIPアドレスで実行されている場合は他のマシンでクライアントに接続できます。この場合、サーバーはポート5000のループバック(127.0.0.1)で実行されます。authkeyパラメーターを使用すると、syncdictを操作するときに安全な接続が使用されます。いずれかのキーが押されると、マネージャーはシャットダウンされます。

Client.py

from multiprocessing.managers import SyncManager
import sys, time

class MyManager(SyncManager):
    pass

MyManager.register("syncdict")

if __name__ == "__main__":
    manager = MyManager(("127.0.0.1", 5000), authkey="password")
    manager.connect()
    syncdict = manager.syncdict()

    print "dict = %s" % (dir(syncdict))
    key = raw_input("Enter key to update: ")
    inc = float(raw_input("Enter increment: "))
    sleep = float(raw_input("Enter sleep time (sec): "))

    try:
         #if the key doesn't exist create it
         if not syncdict.has_key(key):
             syncdict.update([(key, 0)])
         #increment key value every sleep seconds
         #then print syncdict
         while True:
              syncdict.update([(key, syncdict.get(key) + inc)])
              time.sleep(sleep)
              print "%s" % (syncdict)
    except KeyboardInterrupt:
         print "Killed client"

クライアントはまた、カスタマイズされたSyncManagerを作成し、「syncdict」を登録する必要があります。今回は、共有dictを取得するための呼び出し可能オブジェクトを渡さないでください。次に、カスタマイズされたSycnManagerを使用して、ポート5000のループバックIPアドレス(127.0.0.1)と、Server.pyで開始されたマネージャーへの安全な接続を確立するauthkeyを使用して接続します。マネージャに登録されているcallableを呼び出すことにより、共有dictsyncdictを取得します。ユーザーに次のプロンプトを表示します。

  1. 操作するsyncdictのキー
  2. キーがアクセスする値をサイクルごとにインクリメントする量
  3. 1サイクルあたりのスリープ時間(秒単位)

次に、クライアントはキーが存在するかどうかを確認します。そうでない場合は、syncdictにキーを作成します。次に、クライアントは「エンドレス」ループに入り、キーの値を増分で更新し、指定された量をスリープ状態にし、キーボード割り込みが発生するまでこのプロセスを繰り返すためにのみsyncdictを出力します(Ctrl + C)。

厄介な問題

  1. Managerのregisterメソッドは、managerを起動する前に呼び出す必要があります。そうしないと、Managerのdir呼び出しで、実際に登録されたメソッドがあることが明らかになったとしても、例外が発生します。
  2. Dictのすべての操作は、dictの割り当てではなく、メソッドを使用して実行する必要があります(syncdict ["blast"] = 2は、マルチプロセッシングがカスタムオブジェクトを共有する方法のため、惨めに失敗します)
  3. SyncManagerのdictメソッドを使用すると、厄介な問題#1がSyncManager.dict()によって返されるプロキシの登録と共有を妨げることを除いて、厄介な問題#2を軽減します。 (SyncManager.dict()は、マネージャーが開始された後にのみ呼び出すことができ、レジスターはマネージャーが開始される前にのみ機能するため、SyncManager.dict()は、関数型プログラミングを実行し、プロキシをプロセスに引数として渡す場合にのみ役立ちます。ドキュメントの例はそうです)
  4. サーバーとクライアントの両方を登録する必要がありますが、直感的には、クライアントはマネージャーに接続した後にそれを理解できるように見えます(これをウィッシュリストマルチプロセッシング開発者に追加してください)

閉鎖

私と同じように、この非常に徹底的で少し時間のかかる答えを楽しんでいただけたと思います。 Pyroがそよ風を吹くマルチプロセッシングモジュールでなぜそんなに苦労していたのか、頭に浮かぶのに大変苦労していました。この答えのおかげで、頭に釘を打ちました。これがマルチプロセッシングモジュールを改善する方法についてpythonコミュニティに役立つことを願っています。これは非常に有望であると信じていますが、初期段階では可能なことには達していません。迷惑にもかかわらず説明されている問題これはまだ非常に実行可能な代替手段であり、非常に単純だと思います。SyncManager.dict()を使用して、ドキュメントに示されているように引数としてProcessesに渡すこともできます。これは、場合によってはさらに簡単な解決策になるでしょう。要件それは私には不自然に感じます。

55
manifest

同時書き込みの問題に対する適切な解決策に対応して。私は非常に迅速な調査を行い、 この記事 がロック/セマフォソリューションを示唆していることを発見しました。 ( http://effbot.org/zone/thread-synchronization.htm

この例は辞書に固有のものではありませんが、このアイデアに基づいて辞書を操作するのに役立つクラスベースのラッパーオブジェクトをコーディングできると確信しています。

このようなものをスレッドセーフな方法で実装する必要がある場合は、おそらくPythonセマフォソリューションを使用します(以前のマージ手法が機能しないと仮定します)。セマフォは、ブロッキングの性質があるため、一般にスレッドの効率を低下させます。

サイトから:

セマフォは、より高度なロックメカニズムです。セマフォにはロックフラグではなく内部カウンタがあり、指定された数を超えるスレッドがセマフォを保持しようとした場合にのみブロックされます。セマフォの初期化方法に応じて、これにより、複数のスレッドが同じコードセクションに同時にアクセスできるようになります。

semaphore = threading.BoundedSemaphore()
semaphore.acquire() # decrements the counter
... access the shared resource; work with dictionary, add item or whatever.
semaphore.release() # increments the counter
4
Frank V

「共有辞書」を維持するために別のプロセスを捧げます。たとえば、 xmlrpclib その少量のコードを他のプロセスで利用できるようにし、xmlrpclibを介して公開します。インクリメントを実行するためにkey, incrementを受け取る関数と、keyだけを取り、アプリのニーズに応じてセマンティックの詳細(欠落しているキーなどのデフォルト値がある)を含む値を返す関数。

次に、共有dict専用プロセスを実装するために、任意のアプローチを使用できます。メモリ内に単純なdictを備えたシングルスレッドサーバーから、単純なsqlite DBなどまで、コードから始めることをお勧めします。できるだけ簡単に」(persistent共有dictが必要か、永続性が必要ないかによって異なります)、測定して必要に応じて最適化します。

4
Alex Martelli

そもそも辞書を共有する必要がある理由はありますか?各スレッドに独自のディクショナリのインスタンスを維持させ、スレッド処理の最後にマージするか、定期的にコールバックを使用して個々のスレッドディクショナリのコピーをマージすることができますか?

私はあなたが何をしているのか正確にはわかりませんので、私の書いた計画が逐語的に機能しないかもしれないことを私に留めておいてください。私が提案しているのは、より高レベルの設計アイデアです。

3
Frank V