Python でマルチスレッド アプリケーションを作成するために使用されるモジュールは何ですか? 言語とStackless Pythonによって提供される基本的な同時実行メカニズムは認識していますが、それぞれの長所と短所は何ですか?
7 に答える
複雑さが増す順に:
スレッド化モジュールを使用する
長所:
- 独自のスレッドで任意の関数 (実際には呼び出し可能な関数) を実行するのは非常に簡単です。
- データの共有は簡単ではありませんが (ロックは決して簡単ではありません:)、少なくとも簡単です。
短所:
- Juergenが述べたように、 Python スレッドはインタープリターの状態に実際に同時にアクセスすることはできません (1 つの大きなロック、悪名高いGlobal Interpreter Lockがあります)。これが実際に意味することは、スレッドが I/O バウンド タスク (ネットワーク、ディスクへの書き込み、など) ですが、同時計算を行うにはまったく役に立ちません。
マルチプロセッシングモジュールを使用する
単純な使用例threading
では、各タスクが独自のスレッドではなく独自のプロセスで実行されることを除いて、これは使用とまったく同じように見えます。(ほぼ文字通り: Eli の例を取り上げて、 、 、、および(モジュール) をに置き換えるthreading
とmultiprocessing
、問題なく動作するはずです。)Thread
Process
Queue
multiprocessing.Queue
長所:
- すべてのタスクの実際の並行性 (グローバル インタープリター ロックなし)。
- 複数のプロセッサに拡張でき、複数のマシンに拡張することもできます。
短所:
- プロセスはスレッドよりも低速です。
- プロセス間のデータ共有は、スレッドよりも複雑です。
- メモリは暗黙的に共有されません。明示的に共有するか、変数をピクルして前後に送信する必要があります。これはより安全ですが、より困難です。(問題がますます大きくなっているとすれば、Python 開発者は人々をこの方向に押し進めているようです。)
Twistedなどのイベント モデルを使用する
長所:
- 何をいつ実行するかについて、優先度を非常に細かく制御できます。
短所:
- 優れたライブラリを使用しても、通常、非同期プログラミングはスレッド プログラミングよりも難しく、何が起こるべきかを理解することも、実際に何が起こっているかをデバッグすることも困難です。
いずれの場合も、マルチタスクに関連する問題の多く、特にタスク間でデータを共有する方法というトリッキーな問題を既に理解していることを前提としています。何らかの理由でロックと条件をいつ、どのように使用するかわからない場合は、それらから始めなければなりません。マルチタスキング コードには微妙な点や落とし穴がたくさんあります。開始する前に、概念を十分に理解しておくことをお勧めします。
「偽のスレッド」から外部フレームワークに至るまで、すでにさまざまな回答を得ていますがQueue.Queue
、CPython スレッド化の「秘密のソース」について誰も言及していません。
拡張するには: 純粋な Python の CPU 負荷の高い処理をオーバーラップする必要がない限り (その場合は必要ですがmultiprocessing
、独自のQueue
実装も付属しているため、必要な注意を払って一般的なアドバイス I を適用できます)。 '' を与えています;-)、Python のビルトインで実行できます... しかし、たとえば次のように、アドバイスに従ってthreading
使用すると、はるかにうまく実行されます。
おそらくスレッド化とマルチプロセッシングの主なプラスである共有メモリを「忘れて」ください-うまく機能せず、うまくスケーリングせず、決して持っていません。共有メモリは、サブスレッドを生成する前に1 回セットアップされ、その後変更されないデータ構造にのみ使用します。それ以外の場合は、そのリソースを担当する単一のスレッドを作成し、 Queue
.
通常はロックによって保護すると考えられるすべてのリソースに専用のスレッドを割り当てます: 変更可能なデータ構造またはそのまとまりのあるグループ、外部プロセス (DB、XMLRPC サーバーなど) への接続、外部ファイルなど. その種の専用リソースを持っていない、または必要としない汎用タスク用の小さなスレッド プールを取得します。必要なときにスレッドを生成しないでください。
2 つのスレッド間の通信は常にQueue.Queue
-- メッセージ パッシングの形式であり、マルチプロセッシングの唯一の健全な基盤です (トランザクショナル メモリは有望ですが、Haskell を除いて生産に値する実装を私は知りません)。
1 つのリソース (またはまとまりのある小さなリソース セット) を管理する各専用スレッドは、特定の Queue.Queue インスタンスで要求をリッスンします。プール内のスレッドは、単一の共有 Queue.Queue で待機します (キューは確実にスレッドセーフであり、これで失敗することはありません)。
あるキュー (共有または専用) で要求をキューに入れる必要があるだけのスレッドは、結果を待たずにキューに入れ、先に進みます。最終的に要求の結果または確認を必要とするスレッドは、作成したばかりの Queue.Queue のインスタンスとペア (要求、受信キュー) を待ち行列に入れ、最終的に、続行するために応答または確認が不可欠になると、取得します (待機中) ) 受信キューから。エラー応答だけでなく、実際の応答や確認を受け取る準備ができていることを確認してください (Twisteddeferred
は、この種の構造化された応答を整理するのに優れています!)。
また、Queue を使用して、1 つのスレッドで使用できるリソースのインスタンスを「パーク」することもできますが、一度に複数のスレッド間で共有されることはありません (一部の DBAPI コンポーネントとの DB 接続、他のコンポーネントとのカーソルなど) -- これにより、リラックスできます。より多くのプーリングを支持する専用スレッドの要件 (キュー可能なリソースを必要とする要求を共有キューから取得するプール スレッドは、適切なキューからそのリソースを取得し、必要に応じて待機するなど)。
Twisted は実際には、このメヌエット (場合によってはスクエア ダンス) を整理するのに適した方法です。これは、deferred のおかげだけでなく、健全で堅実でスケーラブルな基本アーキテクチャのおかげでもあります。通常はスレッドに値すると見なされるほとんどのことを単一のイベント駆動型スレッドで実行しながら、真に保証されます。
しかし、私は Twisted が万人向けではないことを認識しています。「リソースを専用またはプールし、ワズーをキューに入れ、ロックを必要とすることは何も行わないか、Guido は禁止し、セマフォや条件などのさらに高度な同期手順は行わない」というアプローチが可能です。非同期イベント駆動型の方法論に頭を悩ませることができない場合でも、引き続き使用され、これまでに遭遇した他の広く適用可能なスレッド化アプローチよりも優れた信頼性とパフォーマンスを提供します.
何をしようとしているのかにもよりますthreading
が、標準ライブラリのモジュールを使用することに部分的です。これにより、任意の関数を取得して別のスレッドで実行することが非常に簡単になるからです。
from threading import Thread
def f():
...
def g(arg1, arg2, arg3=None):
....
Thread(target=f).start()
Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()
等々。Queue
モジュールによって提供される同期キューを使用してプロデューサー/コンシューマーを設定することがよくあります
from Queue import Queue
from threading import Thread
q = Queue()
def consumer():
while True:
print sum(q.get())
def producer(data_source):
for line in data_source:
q.put( map(int, line.split()) )
Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start()
for i in range(10):
Thread(target=consumer).start()
Kamaeliaは、多くの通信プロセスを持つアプリケーションを構築するための Python フレームワークです。
これは Pycon 2009 のビデオです。Kamaelia をTwistedおよびParallel Pythonと比較することから始まり、Kamaelia の実践的なデモンストレーションを行います。
(ソース: kamaelia.org ) Kamaelia - 便利で楽しい同時実行性
Kamaelia では、相互に通信する単純なコンポーネントからシステムを構築します。これにより、開発がスピードアップし、メンテナンスが大幅に支援され、自然に並行ソフトウェアを構築できるようになります。初心者を含むすべての開発者がアクセスできるように設計されています。それはまたそれを楽しくします:)
どのようなシステムですか?ネットワーク サーバー、クライアント、デスクトップ アプリケーション、pygame ベースのゲーム、トランスコード システムとパイプライン、デジタル TV システム、スパム撲滅ツール、教育ツール、その他多数 :)
Kamaelia を使用した簡単な並行処理 - パート 1 (59:08) Kamaelia
を使用した簡単な並行処理 - パート 2 (18:15)
カマエリアに関しては、上記の答えはここでのメリットを実際にはカバーしていません。Kamaeliaのアプローチは、並行性のために単一のシステムでスレッド、ジェネレーター、およびプロセスを処理するための、実用的ではなく実用的な統合インターフェースを提供します。
基本的に、それは受信トレイと送信トレイを持つ実行中のもののメタファーを提供します。メッセージを送信トレイに送信し、相互に接続すると、メッセージは送信トレイから受信トレイに流れます。このメタファー/APIは、ジェネレーター、スレッド、プロセスを使用している場合でも、他のシステムと通信している場合でも同じです。
「完全ではない」部分は、受信トレイと送信トレイにまだ糖衣構文が追加されていないためです(これは議論中ですが)-システムの安全性/使いやすさに焦点が当てられています。
上記のベアスレッドを使用した生産者/消費者消費者の例をとると、これはカマエリアでは次のようになります。
Pipeline(Producer(), Consumer() )
この例では、これらがスレッド化されたコンポーネントであるかどうかは関係ありません。使用法の観点から見た場合の唯一の違いは、コンポーネントの基本クラスです。ジェネレーターコンポーネントはリストを使用して通信し、スレッド化されたコンポーネントはQueue.Queuesを使用して通信し、プロセスはos.pipesを使用して通信します。
ただし、このアプローチの背後にある理由は、バグのデバッグを困難にするためです。スレッド化(または共有メモリの同時実行性)で直面する最大の問題は、共有データの更新が誤って壊れていることです。メッセージパッシングを使用することで、 1つのクラスのバグを排除できます。
どこでもベアスレッドとロックを使用する場合は、通常、コードを作成するときに間違いを犯さないという前提で作業しています。私たちは皆それを望んでいますが、それが起こることは非常にまれです。ロック動作を1つの場所にまとめることで、問題が発生する可能性のある場所を単純化できます。(コンテキストハンドラーは役立ちますが、コンテキストハンドラーの外部での偶発的な更新には役立ちません)
明らかに、すべてのコードをメッセージパッシングと共有スタイルとして記述できるわけではありません。そのため、Kamaeliaには単純なソフトウェアトランザクショナルメモリ(STM)もあります。これは、厄介な名前の非常に優れたアイデアです。つまり、変数のバージョン管理に似ています。いくつかの変数をチェックアウトし、それらを更新して、コミットし直してください。衝突した場合は、すすぎ、繰り返します。
関連リンク:
とにかく、それがお役に立てば幸いです。FWIW、Kamaeliaのセットアップの背後にある主な理由は、尾を振ることなく、Pythonシステムで並行性をより安全で使いやすくすることです。(つまり、コンポーネントの大きなバケツ
他のカマエリアの回答が変更された理由は理解できます。私にとっても、回答というよりは広告のように見えるからです。カマエリアの作者として、熱意を見るのは素晴らしいことですが、これにはもう少し関連性の高いコンテンツが含まれていることを願っています:-)
これが私の言い方です。この答えは定義上偏っていることに注意してください。しかし、私にとって、カマエリアの目的は、IMOのベストプラクティスをまとめることです。いくつかのシステムを試してみて、どれが効果的かを確認することをお勧めします。(これがスタックオーバーフローに不適切な場合も、申し訳ありません-私はこのフォーラムに不慣れです:-)