multiprocessingモジュール
multiprocessingモジュールは、Python2系列では2.6以降、3系列では3.0以降に標準となったモジュールです。このモジュールはthreadingモジュールに似たAPIでプロセス間通信などの機能を提供します。このモジュールにより、GILの問題を回避することができ、複数のCPUやCPUコアの性能を生かすことができます。また、このモジュールはローカルのみならず、リモートでのプロセス間通信も行うことが可能で、簡単に分散処理などを実装することができます。
まず、GIL(Global Interpreter Lock)とはPythonのインタプリタ上で一度に1つのスレッドだけが動作するよう保証するためのロックです。このロックによって、同時に同じメモリにアクセスするスレッドが存在しないことを保証します。
しかしながら、このロックによって、一度に1つのスレッドしか実行されなくなるので、デュアルコアのCPUなどのリソースを最大限生かすことができなくなっています。そこで、プロセスを分けて、GIL問題を回避しようというのが、このmultiprocessingモジュールです。
実際にmultiprocessingモジュールを使ったプログラムが速くなるのかどうか簡単なプログラムで比較してみました。
リスト1とリスト2は共に、10000000までカウントアップするプログラムです。リスト1はthreadingモジュール、リスト2はmultiprocessingモジュールで実装しています。また、プログラムの最後にそれぞれのカウントアップに要した時間を表示しています。
実行結果はデュアルコアのCPUを乗せたマシンでの結果です。
リスト1を実行した際にはCPUの利用率が100%まで行かず、CPUリソースを100%使いきっていませんでした。また、リスト2を実行した際にはCPUの利用率が100%まで行っており、CPUを有効に使っていました。その他、threading版はmultiprocessing版の2倍以上の時間差があることから、PythonのインタプリタではGILの取得にかかるコストが大きいことがわかります。
マルチプロセス版map関数
Pythonにはiteratableなオブジェクトの要素に対して、それぞれ関数を適用するmap関数があります。これをマルチプロセスで行うものがmultiprocessingモジュールのPoolクラスに実装されています。
まず、Poolオブジェクトを作成し、使用するプロセスの上限を定義します。要素数の方がPoolで設定した数よりも大い場合は、設定した数以上のプロセスは生成されず、他の要素への処理が終わり次第、プロセスを使いまわして処理を行います。
リスト3はリストの要素の2乗を返すようなプログラムです。
リモートでのプロセス間通信
multiprocessingモジュールはプロセス間の処理だけではなく、リモートでのプロセス間通信もサポートしています。
リスト4ではプロセス間で共有するQueueオブジェクトを作成し、それを取得するためのメソッドを定義しています。
リスト4のマネージャに対して接続し、処理を行うプログラムがリスト5です。リスト5のプログラムはマネージャのQueueに対して自分のホスト名をputして終了します。また、IPアドレスが「192.168.1.1」となっている部分は、リスト4を実行しているマシンのアドレスに変えてください。
リスト4を実行した後に、リスト5を実行します。リスト5が終了した後に、リスト4の実行にリスト5を実行したホストの名前が表示されれば成功です。
このように、簡単にリモートのプロセス間でのオブジェクトの共有を行うことができます。
おわりに
multiprocessingモジュールを使うと、簡単にプロセス間通信や分散処理などを実装することができます。今までスレッドを使っても思うように速度が出ないとか思っていた人は、挑戦してみる価値があるのではないでしょうか?
multiprocessingモジュールは非常に多くの機能を提供しています。multiprocessingが提供するこの他の機能についてはリファレンスをご覧いただければと思います。