1. ホーム
  2. python

[解決済み] スレッディング・モジュールとマルチプロセッシング・モジュールの違いは何ですか?

2022-04-16 16:59:44

質問

の使い方を勉強しています。 threadingmultiprocessing モジュールを使って、特定の処理を並列に実行し、私のコードを高速化することができます。

私は、(多分それについて理論的な背景を持っていないため)このことを理解するのは難しいと感じています。 threading.Thread() オブジェクトと multiprocessing.Process() の1つです。

また、ジョブのキューをインスタンス化して、そのうちの4つだけを(たとえば)並行して実行させ、他のジョブはリソースの解放を待ってから実行する方法は、私にはまったく理解できません。

ドキュメントの例はわかりやすいのですが、あまり網羅的ではありません。少し物事を複雑にしようとすると、すぐに多くの奇妙なエラー(pickleできないメソッドなど)が発生します。

では、どのような場合に threadingmultiprocessing モジュールですか?

この2つのモジュールの背後にあるコンセプトと、複雑なタスクに適切に使用する方法を説明したリソースをいくつかリンクしてもらえますか?

どのように解決するのですか?

ジュリオ・フランコのコメント は、マルチスレッドとマルチプロセシングに当てはまります。 一般的に .

ただし、Python * には、さらに問題があります。グローバルインタープリターロックというものがあり、同じプロセス内の2つのスレッドが同時にPythonのコードを実行できないようになっているのです。つまり、8つのコアを持っていて、8つのスレッドを使うようにコードを変更した場合、800%のCPUを使って8倍速く動作することはできず、同じ100%のCPUを使って同じ速度で動作することになります。(実際には、共有データがない場合でも、スレッドによる余分なオーバーヘッドがあるため、少し遅くなりますが、今は無視してください)。

これには例外があります。もしあなたのコードの重い計算が実際にはPythonで行われず、numpyアプリのような適切なGIL処理を行うカスタムCコードを持つライブラリで行われるなら、スレッド化から期待通りの性能上の利益を得ることができます。同じことは、重い計算が、あなたが実行し、待機するサブプロセスによって行われる場合にも当てはまります。

さらに重要なのは、これが問題にならないケースもあることだ。例えば、ネットワークサーバーはネットワークからパケットを読み取ることにほとんどの時間を費やしていますし、GUIアプリはユーザーイベントを待つことにほとんどの時間を費やしています。ネットワーク・サーバーやGUIアプリでスレッドを使用する理由の1つは、メイン・スレッドがネットワーク・パケットやGUIイベントを処理し続けるのを止めずに、長時間実行するバックグラウンド・タスクを実行できるようにすることです。そして、それはPythonのスレッドでうまく機能します。(技術的な用語では、Pythonのスレッドはコア並列性を与えないけれども、並行性を与えることを意味します。)

しかし、純粋なPythonでCPUに縛られたプログラムを書いている場合、より多くのスレッドを使用することは一般的に有益ではありません。

各プロセスはそれぞれ独立したGILを持つので、プロセスを分けてもそのような問題はありません。もちろん、スレッドとプロセスの間には他の言語と同じようにトレードオフがあります。スレッド間よりもプロセス間でデータを共有する方がより難しく、よりコストがかかります。しかし、GILは、CやJavaなどにはない方法で、プロセスへのバランスに重きを置いています。そのため、CやJavaの場合よりもPythonの方が、マルチプロセシングを頻繁に使用することになるでしょう。


一方、Pythonの"battery included"の哲学は、いくつかの良い知らせをもたらします:ワンライナーの変更で、スレッドとプロセスの間を行き来できるコードを非常に簡単に書くことができます。

入出力以外を他のジョブ(あるいはメイン・プログラム)と共有しない自己完結型の "jobs"でコードを設計すれば concurrent.futures ライブラリを使って、このようにスレッドプールを中心にコードを記述します。

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

これらのジョブの結果を取得して、さらに別のジョブに渡したり、実行順や完了順に物事を待ったりすることもできます。 Future オブジェクトを使用します。

さて、もしあなたのプログラムが常に100%のCPUを使っていて、スレッドを増やしても遅くなるだけだとわかったら、GILの問題にぶつかっていることになるので、プロセスに切り替える必要があります。この最初の行を変更するだけでよいのです。

with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

唯一の注意点は、ジョブの引数や戻り値がpickle可能でなければならないことです(pickleに多くの時間やメモリを必要としないこと)。通常は問題にはなりませんが、時には問題になることもあります。


しかし、ジョブが自己完結できない場合はどうでしょうか?もし、ジョブが自己完結しないようなコードを設計できるのであれば、次のようになります。 メッセージの受け渡し というように、互いに影響し合うので、まだかなり簡単です。ただし threading.Thread または multiprocessing.Process プールに頼らず そして queue.Queue または multiprocessing.Queue オブジェクトを明示的に作成します。(パイプ、ソケット、フロックファイルなど、他にもたくさんの選択肢がありますが、重要なことは、あなたは 何か エクゼキュータの自動マジックが不十分な場合は、手動で行います)。

しかし、メッセージパッシングにさえ頼れない場合はどうでしょう?2つのジョブが同じ構造を変更し、互いの変更を見る必要がある場合はどうでしょうか?その場合、手動で同期(ロック、セマフォ、条件など)を行う必要があり、プロセスを使用したい場合は、起動時に明示的な共有メモリオブジェクトを使用する必要があります。これがマルチスレッド(またはマルチプロセシング)が難しくなる理由です。もし、あなたがそれを避けることができるなら、素晴らしいことです。もし、あなたがそれを避けることができないなら、誰かがSOの答えに入れることができる以上のものを読む必要があります。


コメントから、Pythonのスレッドとプロセスは何が違うのか知りたいということでした。Giulio Francoの答えと私の答え、そして私たちのリンクをすべて読めば、本当にすべてがわかるはずです...しかし、要約は間違いなく役に立つでしょうから、ここに行きます。

  1. スレッドはデフォルトでデータを共有しますが、プロセスはそうではありません。
  2. (1)の結果、一般的にプロセス間でデータを送信する場合、picklingとunpicklingを行う必要があります。 **
  3. (1)のもう一つの帰結として、プロセス間で直接データを共有するには、一般に、データをValue、Array、およびMySQLのような低レベルの形式にする必要があります。 ctypes 型があります。
  4. プロセスはGILの対象外です。
  5. 一部のプラットフォーム(主にWindows)では、プロセスの作成と破棄に非常に多くのコストがかかります。
  6. プロセスにはいくつかの特別な制限があり、そのいくつかはプラットフォームによって異なります。参照 プログラミング・ガイドライン をご覧ください。
  7. threading モジュールの機能のいくつかは持っていません。 multiprocessing モジュールになります。(このモジュールでは multiprocessing.dummy を使えば、足りないAPIのほとんどをスレッドの上に乗せることができますし、 より上位のモジュールである concurrent.futures で、気にすることはありません)。

<サブ * この問題があるのは、実はPythonという言語ではなく、CPythonという言語の標準的な実装なのです。他の実装では、JythonのようにGILを持っていないものもあります。

<サブ ** もし、あなたが フォーク 各子プロセスは、子プロセスが開始されたときに親プロセスが持っていたすべてのリソースを取得し、子プロセスにデータを渡す別の方法とすることができます。