バージョン 2.6 で追加.
multiprocessing はPythonの標準ライブラリのパッケージで threading とよく似た API を使ってプロセスを生成することができます。 multiprocessing パッケージを使用すると、ローカルとリモート両方の並列制御を行うことができます。また、このパッケージはスレッドの代わりにサブプロセスを使用することにより、グローバルインタプリタロック ( Global Interpreter Lock ) の問題を避ける工夫が行われています。このような特徴があるため multiprocessing モジュールを使うことで、マルチプロセッサマシンの性能を最大限に活用することができるでしょう。なお、このモジュールは Unix と Windows で動作します。
警告
このパッケージに含まれる機能には、ホストとなるオペレーティングシステム上で動作している共有セマフォ (shared semaphore) を使用しているものがあります。これが使用できない場合には、 multiprocessing.synchronize モジュールが無効になり、このモジュールのインポート時に ImportError が発生します。詳細は issue 3770 を参照してください。
ノート
このパッケージに含まれる機能を使用するためには、子プロセスから __main__ モジュールを呼び出せる必要があります。このことについては プログラミングガイドライン で触れていますが、ここであらためて強調しておきます。何故かというと、いくつかのサンプルコード、例えば multiprocessing.Pool のサンプルはインタラクティブシェル上では動作しないからです。以下に例を示します。
>>> from multiprocessing import Pool
>>> p = Pool(5)
>>> def f(x):
... return x*x
...
>>> p.map(f, [1,2,3])
Process PoolWorker-1:
Process PoolWorker-2:
Process PoolWorker-3:
Traceback (most recent call last):
AttributeError: 'module' object has no attribute 'f'
AttributeError: 'module' object has no attribute 'f'
AttributeError: 'module' object has no attribute 'f'
(このサンプルを試すと、3つのトレースバック全てがほぼランダムに交互に重なって表示されます。そうなったら、なんとかしてマスタープロセスを止めましょう。)
multiprocessing モジュールでは、プロセスは以下の手順によって生成されます。はじめに Process のオブジェクトを作成し、続いて start() メソッドを呼び出します。この Process クラスは threading.Thread クラスと同様の API を持っています。まずは、簡単な例をもとにマルチプロセスを使用したプログラムについてみていきましょう。
from multiprocessing import Process
def f(name):
print 'hello', name
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
実行された個々のプロセス ID を表示するために拡張したサンプルコードを以下に例を示します。
from multiprocessing import Process
import os
def info(title):
print title
print 'module name:', __name__
print 'parent process:', os.getppid()
print 'process id:', os.getpid()
def f(name):
info('function f')
print 'hello', name
if __name__ == '__main__':
info('main line')
p = Process(target=f, args=('bob',))
p.start()
p.join()
(Windows 環境で) if __name__ == '__main__' という文が必要な理由については、 プログラミングガイドライン を参照してください。
multiprocessing モジュールでは、プロセス間通信の手段が2つ用意されています。それぞれ以下に詳細を示します。
キュー (Queue)
Queue クラスは Queue.Queue クラスとほとんど同じように使うことができます。以下に例を示します。
from multiprocessing import Process, Queue def f(q): q.put([42, None, 'hello']) if __name__ == '__main__': q = Queue() p = Process(target=f, args=(q,)) p.start() print q.get() # "[42, None, 'hello']" を表示 p.join()キューはスレッドセーフであり、プロセスセーフです。
パイプ (Pipe)
Pipe() 関数は接続用オブジェクトのペアを返します。デフォルトでは、このオブジェクトを介して、親子間でパイプを使った双方向通信をおこなうことができます。以下に例を示します。
from multiprocessing import Process, Pipe def f(conn): conn.send([42, None, 'hello']) conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print parent_conn.recv() # "[42, None, 'hello']" を表示 p.join()2つのコネクション用オブジェクトが Pipe() 関数から返され、親側の入出力、子側の入出力といったように、それぞれパイプの両端となります。 (他プロセスと通信する方法として) 各接続用オブジェクトには、 send() メソッドと recv() メソッドがあります。データを破壊してしまうような使い方に注意する必要があります。それは、2つのプロセス (もしくはスレッド) が同時に 同じ パイプに対して、読み書きをおこなった場合に起こります。もちろん、異なったパイプを使用していれば、同時に読み書きをおこなってもデータが破壊されてしまう危険性はありません。
multiprocessing は threading モジュールと同じプロセス間同期の仕組みを備えています。以下の例では、ロックを使用して、一度に1つのプロセスしか標準出力に書き込まないようにしています。
from multiprocessing import Process, Lock
def f(l, i):
l.acquire()
print 'hello world', i
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
ロックを使用しないで標準出力に書き込んだ場合は、各プロセスからの出力がごちゃまぜになってしまいます。
これまでの話の流れで触れたとおり、並列プログラミングをする時には、出来る限り状態を共有しないというのが定石です。複数のプロセッサを使用するときは特にそうでしょう。
しかし、どうしてもプロセス間のデータ共有が必要な場合のために multiprocessing モジュールにはその方法が用意されています。
共有メモリ (Shared memory)
データを共有メモリ上に保持するために Value クラス、もしくは Array クラスを使用することができます。以下のサンプルコードを使って、この機能についてみていきましょう。
from multiprocessing import Process, Value, Array def f(n, a): n.value = 3.1415927 for i in range(len(a)): a[i] = -a[i] if __name__ == '__main__': num = Value('d', 0.0) arr = Array('i', range(10)) p = Process(target=f, args=(num, arr)) p.start() p.join() print num.value print arr[:]このサンプルコードを実行すると以下のように表示されます。
3.1415927 [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]num と arr を生成するときに使用されている 'd' と 'i' の引数は array モジュールにより使用される種別の型コードです。ここで使用されている 'd' は倍精度浮動小数、 'i' は符号付整数を表します。これらの共有オブジェクトは、プロセスセーフでありスレッドセーフです。
共有メモリを使用して、さらに柔軟なプログラミングを行うには multiprocessing.sharedctypes モジュールを使用します。このモジュールは共有メモリから割り当てられた任意の ctypes オブジェクトの生成をサポートします。
サーバプロセス (Server process)
Manager() 関数により生成されたマネージャオブジェクトはサーバプロセスを管理します。マネージャオブジェクトは Python のオブジェクトを保持して、他のプロセスがプロキシ経由でその Python オブジェクトを操作することができます。
Manager() 関数が返すマネージャは list 、 dict 、 Namespace 、 Lock 、 RLock 、 Semaphore 、 BoundedSemaphore 、 Condition 、 Event 、 Queue 、 Value 、 Array をサポートします。以下にサンプルコードを示します。
from multiprocessing import Process, Manager def f(d, l): d[1] = '1' d['2'] = 2 d[0.25] = None l.reverse() if __name__ == '__main__': manager = Manager() d = manager.dict() l = manager.list(range(10)) p = Process(target=f, args=(d, l)) p.start() p.join() print d print lこのサンプルコードを実行すると以下のように表示されます。
{0.25: None, 1: '1', '2': 2} [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]サーバプロセスのマネージャオブジェクトは共有メモリのオブジェクトよりも柔軟であるといえます。それは、どのような型のオブジェクトでも使えるからです。また、1つのマネージャオブジェクトはネットワーク経由で他のコンピュータ上のプロセスによって共有することもできます。しかし、共有メモリより動作が遅いという欠点があります。
Pool クラスは、ワーカープロセスをプールする機能を備えています。このクラスには、いくつかの方法で開放されたワーカープロセスへタスクを割り当てるメソッドがあります。
以下に例を示します。
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
pool = Pool(processes=4) # 4つのワーカープロセスで開始
result = pool.apply_async(f, [10]) # 非同期で "f(10)" を評価
print result.get(timeout=1) # あなたのコンピュータが *かなり* 遅くない限りは "100" を表示
print pool.map(f, range(10)) # "[0, 1, 4,..., 81]" を表示
multiprocessing パッケージは threading モジュールの API とほとんど同じです。
Process オブジェクトは各プロセスの振る舞いを表します。 Process クラスは threading.Thread クラスの全てのメソッドと同じインタフェースを提供します。
コンストラクタは必ずキーワード引数で呼び出すべきです。引数 group には必ず None を渡してください。この引数は threading.Thread クラスとの互換性のためだけに残されています。引数 target には、呼び出し可能オブジェクト (Collable Object) を渡します。このオブジェクトは run() メソッドから呼び出されます。この引数はデフォルトで None となっており、何も呼び出されません。引数 name にはプロセス名を渡します。デフォルトでは、自動でユニークな名前が割り当てられます。命名規則は、 ‘Process-N1:N2:...:Nk‘ となります。ここで N1,N2,...,Nk は整数の数列で、 作成した プロセス数に対応します。引数 args は target で指定された呼び出し可能オブジェクトへの引数を渡します。同じく、引数 kwargs はキーワード引数を渡します。デフォルトでは、 target には引数が渡されないようになっています。
サブクラスがコンストラクタをオーバーライドする場合は、そのプロセスに対する処理を行う前に基底クラスのコンストラクタ (Process.__init__()) を実行しなければなりません。
プロセスが実行する処理を表すメソッドです。
このメソッドはサブクラスでオーバーライドすることができます。標準の run() メソッドは呼び出し可能オブジェクトを呼び出します。この呼び出されるオブジェクトはコンストラクタの target 引数として渡されます。もしコンストラクタに args もしくは kwargs 引数が渡されていれば、呼び出すオブジェクトにこれらの引数を渡します。
join() されたプロセスが terminate を呼び出すまで、もしくはオプションで指定したタイムアウトが発生するまで呼び出し側のスレッドをブロックします。
timeout が None ならタイムアウトは設定されません。
1つのプロセスは何回も join することができます。
プロセスは自分自身を join することはできません。それはデッドロックを引き起こすからです。プロセスが start される前に join しようとするとエラーが発生します。
プロセス名です。
この名前は文字列で、プロセスの識別にのみ使用されます。特別な命名規則はありません。複数のプロセスが同じ名前を持つ場合もあります。また、この名前はコンストラクタにより初期化されます。
デーモンプロセスであるかどうかのフラグであり、ブール値を設定します。この属性は start() が呼び出される前に設定する必要があります。
初期値は作成するプロセスから継承します。
プロセスが終了するとき、その子プロセスのデーモンプロセス全てを終了させようとします。
デーモンプロセスは子プロセスを作成できないことに注意してください。もしそうでなければ、そのデーモンの親プロセスが終了したときに子プロセスが孤児になってしまう場合があるからです。加えて Unix デーモンまたはサービスで ない 場合には、非デーモンプロセスが終了したとき、普通のプロセスとして (join されずに) 終了します。
Threading.Thread クラスのAPIに加えて Process クラスのオブジェクトには以下の属性およびメソッドがあります。
プロセスIDを返します。プロセスの生成前は None が設定されています。
子プロセスの終了コードです。子プロセスがまだ終了していない場合は None が返されます。負の値 -N は子プロセスがシグナル N で終了したことを表します。
プロセスの認証キーです (バイト文字列です)。
multiprocessing モジュールがメインプロセスにより初期化される場合には、 os.random() 関数を使用してランダムな値が設定されます。
Process クラスのオブジェクトの作成時にその親プロセスから認証キーを継承します。もしくは authkey に別のバイト文字列を設定することもできます。
詳細は 認証キー を参照してください。
プロセスを終了します。Unix 環境では SIGTERM シグナルを、 Windows 環境では TerminateProcess() を使用して終了させます。終了ハンドラや finally 節などは、実行されないことに注意してください。
このメソッドにより終了するプロセスの子孫プロセスは、終了 しません 。そういった子孫プロセスは単純に孤児になります。
警告
このメソッドの使用時に、関連付けられたプロセスがパイプやキューを使用している場合には、使用中のパイプやキューが破損して他のプロセスから使用できなくなる可能性があります。同様に、プロセスがロックやセマフォなどを取得している場合には、このプロセスが終了してしまうと他のプロセスのデッドロックの原因になるでしょう。
プロセスオブジェクトが作成したプロセスのみが start(), join(), is_alive() と exit_code のメソッドを呼び出すべきです。
以下の例では Process のメソッドの使い方を示しています。
>>> import multiprocessing, time, signal
>>> p = multiprocessing.Process(target=time.sleep, args=(1000,))
>>> print p, p.is_alive()
<Process(Process-1, initial)> False
>>> p.start()
>>> print p, p.is_alive()
<Process(Process-1, started)> True
>>> p.terminate()
>>> time.sleep(0.1)
>>> print p, p.is_alive()
<Process(Process-1, stopped[SIGTERM])> False
>>> p.exitcode == -signal.SIGTERM
True
この例外は Connection.recv_bytes_into() によって発生し、バッファオブジェクトが小さすぎてメッセージが読み込めないことを示します。
e が BufferTooShort のインスタンスとすると、 e.args[0] はバイト文字列でそのメッセージを取得できます。
マルチプロセス環境では、一般的にメッセージパッシングをプロセス間通信のために使用し、ロックのような同期プリミティブの使用しないようにします。
メッセージのやりとりのために Pipe() (2つのプロセス間の通信用)、もしくはキュー (複数プロセスがメッセージを生成、消費する通信用) を使用することができます。
Queue と JoinableQueue は複数プロセスから生成/消費を行う FIFO キューです。これらのキューは標準ライブラリの Queue.Queue を模倣しています。 Queue には Python 2.5 の Queue.Queue クラスで導入された task_done() と join() メソッドがないことが違う点です。
もし JoinableQueue を使用するなら、キューから削除される各タスクのために JoinableQueue.task_done() を呼び出さなければ なりません 。もしくは、ある例外を発生させてオーバーフローする可能性がある未終了タスクを数えるためにセマフォが使用されます。
管理オブジェクトを使用することで共有キューを作成できることも覚えておいてください。詳細は Managers を参照してください。
ノート
multiprocessing は通常の Queue.Empty と、タイムアウトのシグナルを送るために Queue.Full 例外を使用します。それらは Queue からインポートする必要があるので multiprocessing の名前空間では利用できません。
警告
Queue を利用しようとしている最中にプロセスを Process.terminate() や os.kill() で終了させる場合、キューにあるデータは破損し易くなります。終了した後で他のプロセスがキューを利用しようとすると、例外を発生させる可能性があります。
警告
上述したように、もし子プロセスがキューへ要素を追加するなら (そして JoinableQueue.cancel_join_thread() を使用しない) そのプロセスはバッファされた全ての要素がパイプへフラッシュされるまで終了しません。
これは、そのプロセスを join しようとする場合、キューに追加された全ての要素が消費されるのを確認しない限り、デッドロックを発生させる可能性があることを意味します。似たような現象で、子プロセスが非デーモンプロセスの場合、親プロセスは終了時に非デーモンの全ての子プロセスを join しようとしてハングアップする可能性があります。
Manager を使用して作成されたキューではこの問題はありません。詳細は プログラミングガイドライン を参照してください。
プロセス間通信におけるキューの使用方法は 例 を参照してください。
パイプの終端を表す Connection オブジェクトのタプル (conn1, conn2) を返します。
duplex が True (デフォルト) なら、双方向性パイプです。 duplex が False なら、パイプは一方向性です。 conn1 はメッセージの受信専用に conn2 はメッセージの送信専用として使用されます。
パイプや2~3個のロック/セマフォを使用して実装されたプロセス共有キューを返します。あるプロセスが最初に要素をキューへ追加するとき、バッファからパイプの中へオブジェクトを転送する供給スレッドが開始されます。
標準ライブラリの Queue モジュールからの通常の Queue.Empty や Queue.Full 例外はタイムアウトのシグナルを送るために発生します。
Queue は task_done() や join() を除く Queue.Queue の全てのメソッドを実装します。
おおよそのキューのサイズを返します。マルチスレッディング/マルチプロセスの特性上、この数値は信用できません。
これは sem_getvalue() が実装されていない Mac OS X のような Unix プラットホーム上で NotImplementedError を発生させる可能性があることを覚えておいてください。
キューが空っぽなら True を、そうでなければ False を返します。マルチスレッディング/マルチプロセスの特性上、これは信用できません。
キューがいっぱいなら True を、そうでなければ False を返します。マルチスレッディング/マルチプロセスの特性上、これは信用できません。
キューの中へ要素を追加します。オプションの引数 block が True (デフォルト) 且つ timeout が None (デフォルト) なら、空きスロットが利用可能になるまで必要であればブロックします。 timeout が正の数なら、最大 timeout 秒ブロックして、その時間内に空きスロットが利用できなかったら Queue.Full 例外を発生させます。それ以外 ( block が False ) で、空きスロットがすぐに利用可能な場合はキューに要素を追加します。そうでなければ Queue.Full 例外が発生します(その場合 timeout は無視されます)。
put(item, False) と等価です。
キューから要素を取り出して削除します。オプションの引数 block が True (デフォルト) 且つ timeout が None (デフォルト) なら、要素が取り出せるまで必要であればブロックします。 timeout が正の数なら、最大 timeout 秒ブロックして、その時間内に要素が取り出せなかったら Queue.Empty 例外を発生させます。それ以外 ( block が False ) で、要素がすぐに取り出せる場合は要素を返します。そうでなければ Queue.Empty 例外が発生します(その場合 timeout は無視されます)。
multiprocessing.Queue は Queue.Queue にはない追加メソッドがあります。これらのメソッドは通常、ほとんどのコードに必要ありません。
カレントプロセスからこのキューへそれ以上データが追加されないことを表します。バックグラウンドスレッドはパイプへバッファされた全てのデータをフラッシュするとすぐに終了します。これはキューがガベージコレクトされるときに自動的に呼び出されます。
バックグラウンドスレッドを join します。このメソッドは close() が呼び出された後でのみ使用されます。バッファされた全てのデータがパイプへフラッシュされるのを保証した上で、バックグラウンドスレッドが終了するまでブロックします。
デフォルトでは、あるプロセスがキューを作成していない場合、終了時にキューのバックグラウンドスレッドを join しようとします。そのプロセスは join_thread() が何もしないように cancel_join_thread() を呼び出すことができます。
join_thread() がブロッキングするのを防ぎます。特にこれはバックグラウンドスレッドがそのプロセスの終了時に自動的に join されるのを防ぎます。詳細は join_thread() を参照してください。
JoinableQueue は Queue のサブクラスであり、 task_done() や join() メソッドが追加されているキューです。
以前にキューへ追加されたタスクが完了したことを表します。キュー消費スレッドによって使用されます。タスクをフェッチするために使用されるそれぞれの get() では、次に task_done() を呼び出してタスクの処理が完了したことをキューへ伝えます。
もし join() がブロッキング状態なら、全ての要素が処理されたときに復帰します( task_done() 呼び出しが全ての要素からキュー内へ put() されたと受け取ったことを意味します)。
キューにある要素より多く呼び出された場合 ValueError が発生します。
キューにある全ての要素が取り出されて処理されるまでブロッキングします。
キューに要素が追加されると未終了タスク数が増えます。キューの要素が取り出されて全て処理が完了したことを表す task_done() を消費スレッドが呼び出すと数が減ります。未終了タスク数がゼロになると join() はブロッキングを解除します。
カレントプロセスのアクティブな子プロセスの全てのリストを返します。
これを呼び出すと “join” して既に終了しているプロセスには副作用があります。
システムの CPU 数を返します。もしかしたら NotImplementedError が発生するかもしれません。
カレントプロセスに対応する Process オブジェクトを返します。
threading.current_thread() とよく似た関数です。
multiprocessing を使用するプログラムが固まったときにウィンドウを実行状態にすることをサポートします。 ( py2exe , PyInstaller や cx_Freeze でテストされています。)
メインモジュールの if __name__ == '__main__' の後でこの関数を連続的に呼び出す必要があります。以下に例を示します。
from multiprocessing import Process, freeze_support
def f():
print 'hello world!'
if __name__ == '__main__':
freeze_support()
Process(target=f).start()
もし freeze_support() の行がない場合、固まった実行状態で実行しようとして RuntimeError を発生させます。
そのモジュールが Python インタプリタによって普通に実行されるなら freeze_support() は何の影響もありません。
子プロセスを開始するときに使用する Python インタプリタのパスを設定します。 (デフォルトでは sys.executable が使用されます)。コードに組み込むときは、おそらく次のようにする必要があります。
setExecutable(os.path.join(sys.exec_prefix, 'pythonw.exe'))
子プロセスを生成する前に行います。 (Windows 専用)
ノート
multiprocessing には threading.active_count(), threading.enumerate(), threading.settrace(), threading.setprofile(), threading.Timer や threading.local のような関数はありません。
Connection オブジェクトは pickle でシリアライズ可能なオブジェクトか文字列を送ったり、受け取ったりします。そういったオブジェクトはメッセージ指向の接続ソケットと考えられます。
Connection オブジェクトは通常は Pipe() を使用して作成されます。詳細は Listeners and Clients も参照してください。
コネクションの向こう側へ recv() を使用して読み込むオブジェクトを送ります。
オブジェクトは pickle でシリアライズ可能でなければなりません。 pickle が極端に大きすぎる (OS にも依りますが、およそ 32 MB+) と、 ValueError 例外が送出されることがあります。
コネクションが使用するハンドラか、ファイルディスクリプタを返します。
コネクションをクローズします。
コネクションがガベージコレクトされるときに自動的に呼び出されます。
読み込み可能なデータがあるかどうかを返します。
timeout が指定されていなければすぐに返します。 timeout に数値を指定すると、最大指定した秒数をブロッキングします。 timeout に None を指定するとタイムアウトせずにずっとブロッキングします。
バッファインタフェースをサポートするオブジェクトから完全なメッセージとしてバイトデータを送ります。
offset が指定されると buffer のその位置からデータが読み込まれます。 size が指定されると大量データがバッファから読み込まれます。
文字列のようにコネクションの向こう側から送られたバイトデータの完全なメッセージを返します。何も受け取らずにコネクションの向こう側でクローズされた場合 EOFError が発生します。
maxlength を指定して、且つ maxlength よりメッセージが長い場合、 IOError を発生させて、それ以上はコネクションから読み込めなくなります。
コネクションの向こう側から送られたバイトデータを buffer に読み込み、メッセージのバイト数を返します。何も受け取らずにコネクションの向こう側でクローズされた場合 EOFError が発生します。
buffer は書き込み可能なバッファインタフェースを備えたオブジェクトでなければなりません。 offset が与えられたら、その位置からバッファへメッセージが書き込まれます。オフセットは buffer バイトよりも小さい正の数でなければなりません。
バッファがあまりに小さいと BufferTooShort 例外が発生します。 e が例外インスタンスとすると完全なメッセージは e.args[0] で確認できます。
例:
>>> from multiprocessing import Pipe
>>> a, b = Pipe()
>>> a.send([1, 'hello', None])
>>> b.recv()
[1, 'hello', None]
>>> b.send_bytes('thank you')
>>> a.recv_bytes()
'thank you'
>>> import array
>>> arr1 = array.array('i', range(5))
>>> arr2 = array.array('i', [0] * 10)
>>> a.send_bytes(arr1)
>>> count = b.recv_bytes_into(arr2)
>>> assert count == len(arr1) * arr1.itemsize
>>> arr2
array('i', [0, 1, 2, 3, 4, 0, 0, 0, 0, 0])
警告
Connection.recv() メソッドは受信したデータを自動的に unpickle 化します。それはメッセージを送ったプロセスが信頼できる場合を除いてセキュリティリスクになります。
そのため Pipe() を使用してコネクションオブジェクトを生成する場合を除いて、何らかの認証処理を実行した後で recv() や send() メソッドのみを使用すべきです。詳細は 認証キー を参照してください。
警告
もしプロセスがパイプの読み込み又は書き込み中に kill されると、メッセージの境界が正しいかどうか分からないので、そのパイプのデータは破壊されたようになります。
一般的に同期プリミティブはマルチスレッドプログラムのようにマルチプロセスプログラムでは必要ありません。詳細は threading モジュールのドキュメントを参照してください。
マネージャオブジェクトを使用して同期プリミティブを作成できることも覚えておいてください。詳細は Managers を参照してください。
束縛されたセマフォオブジェクト: threading.BoundedSemaphore のクローンです。
(Mac OS X では sem_getvalue() が実装されていないので Semaphore と区別がつきません。)
状態変数: threading.Condition のクローンです。
lock を指定するなら multiprocessing の Lock か RLock オブジェクトにすべきです。
threading.Event のクローンです。このメソッドは、終了時の内部セマフォの状態を返すので、タイムアウトが与えられ、実際にオペレーションがタイムアウトしたのでなければ、必ず True を返します。
バージョン 2.7 で変更: 以前は、このメソッドは必ず None を返していました。
非再帰的なロックオブジェクト: threading.Lock のクローンです。
再帰的なロックオブジェクト: threading.RLock のクローンです。
セマフォオブジェクト: threading.Semaphore のクローンです。
ノート
BoundedSemaphore, Lock, RLock と Semaphore の acquire() メソッドは threading ではサポートされていないタイムアウトパラメータを取ります。その引数はキーワード引数で受け取れる acquire(block=True, timeout=None) です。 block が True 且つ timeout が None ではないなら、タイムアウトが秒単位で設定されます。 block が False なら timeout は無視されます。
Mac OS X では sem_timedwait がサポートされていないので、 acquire() タイムアウトを与えて呼ぶと、擬似的なスリーピングループ関数を実行することになります。
ノート
メインスレッドが BoundedSemaphore.acquire(), Lock.acquire(), RLock.acquire(), Semaphore.acquire(), Condition.acquire() 又は Condition.wait() を呼び出してブロッキング状態のときに Ctrl-C で生成される SIGINT シグナルを受け取ると、その呼び出しはすぐに中断されて KeyboardInterrupt が発生します。
これは同等のブロッキング呼び出しが実行中のときに SIGINT が無視される threading の振る舞いとは違っています。
子プロセスにより継承される共有メモリを使用する共有オブジェクトを作成することができます。
共有メモリから割り当てられた ctypes オブジェクトを返します。デフォルトでは、返り値は実際のオブジェクトの同期ラッパーです。
typecode_or_type は返されるオブジェクトの型を決めます。それは ctypes の型か array モジュールで使用されるような1文字の型コードかのどちらか一方です。 *args は型のコンストラクタへ渡されます。
lock が True (デフォルト) なら、値へ同期アクセスするために新たなロックオブジェクトが作成されます。 lock が Lock か RLock なら値への同期アクセスに使用されます。 lock が False なら、返されたオブジェクトへのアクセスはロックにより自動的に保護されません。そのため、必ずしも “プロセスセーフ” ではありません。
lock はキーワード引数でのみ指定することに注意してください。
共有メモリから割り当てられた ctypes 配列を返します。デフォルトでは、返り値は実際の配列の同期ラッパーです。
typecode_or_type は返される配列の要素の型を決めます。それは ctypes の型か array モジュールで使用されるような1文字の型コードかのどちらか一方です。 size_or_initializer が整数なら、配列の長さを決定し、その配列はゼロで初期化されます。別の使用方法として size_or_initializer は配列の初期化に使用されるシーケンスになり、そのシーケンス長が配列の長さを決定します。
lock が True (デフォルト) なら、値へ同期アクセスするために新たなロックオブジェクトが作成されます。 lock が Lock か RLock なら値への同期アクセスに使用されます。 lock が False なら、返されたオブジェクトへのアクセスはロックにより自動的に保護されません。そのため、必ずしも “プロセスセーフ” ではありません。
lock はキーワード引数でのみ指定することに注意してください。
ctypes.c_char の配列は文字列を格納して取り出せる value と raw 属性を持っていることを覚えておいてください。
Manager は別のプロセス間で共有されるデータの作成方法を提供します。マネージャオブジェクトは 共有オブジェクト を管理するサーバプロセスを制御します。他のプロセスはプロキシ経由で共有オブジェクトへアクセスすることができます。
プロセス間で共有オブジェクトのために使用される SyncManager オブジェクトを返します。返されたマネージャオブジェクトは生成される子プロセスに対応して、共有オブジェクトを作成するメソッドを持ち、応答プロキシを返します。
マネージャプロセスは親プロセスが終了するか、ガベージコレクトされると停止します。マネージャクラスは multiprocessing.managers モジュールで定義されています。
BaseManager オブジェクトを作成します。
作成後、マネージャオブジェクトが開始されたマネージャプロセスの参照を保証するために start() か get_server().serve_forever() を呼び出します。
address はマネージャプロセスが新たなコネクションを待ち受けるアドレスです。 address が None の場合、任意のアドレスが設定されます。
authkey はサーバプロセスへ接続しようとするコネクションの正当性を検証するために使用される認証キーです。 authkey が None の場合 current_process().authkey が使用されます。 authkey を使用する場合は文字列でなければなりません。
マネージャを開始するためにサブプロセスを開始します。 initializer が None でなければ、サブプロセスは開始時に initializer(*initargs) を呼び出します。
マネージャの制御下にある実際のサーバに相当する Server オブジェクトを返します。 Server オブジェクトは serve_forever() メソッドをサポートします。
>>> from multiprocessing.managers import BaseManager
>>> manager = BaseManager(address=('', 50000), authkey='abc'))
>>> server = manager.get_server()
>>> server.serve_forever()
Server はさらに address 属性も持っています。
ローカルからリモートのマネージャオブジェクトへ接続します。
>>> from multiprocessing.managers import BaseManager
>>> m = BaseManager(address=('127.0.0.1', 5000), authkey='abc))>>>
m.connect()
マネージャクラスで呼び出し可能オブジェクト(callable)や型を登録するために使用されるクラスメソッドです。
typeid は特に共有オブジェクトの型を識別するために使用される “型識別子” です。これは文字列でなければなりません。
callable はこの型識別子のオブジェクトを作成するために使用される呼び出し可能オブジェクトです。マネージャインスタンスが from_address() クラスメソッドを使用して作成されるか、 create_method 引数が False の場合は None でも構いません。
proxytype はこの typeid で共有オブジェクトのプロキシを作成するために使用される BaseProxy のサブクラスです。 None の場合、プロキシクラスは自動的に作成されます。
exposed は BaseProxy._callMethod() を使用してアクセスされるこの typeid のプロキシになるメソッド名のシーケンスを指定するために使用されます。 ( exposed が None の場合 proxytype._exposed_ が存在すれば、それが代わりに使用されます。) exposed リストが指定されない場合は、共有オブジェクトの全ての “パブリックメソッド” にアクセスされます。 (ここで言う “パブリックメソッド” は __call__() メソッドを持ち、 '_' で始まらない名前の属性を意味します。)
method_to_typeid はプロキシが返す exposed メソッドの型を指定するために使用されるマッピングです。それは typeid 文字列に対してメソッド名をマップします。 ( method_to_typeid が None の場合 proxytype._method_to_typeid_ が存在すれば、それが代わりに使用されます。) メソッド名がこのマッピングのキーではないか、マッピングが None の場合、そのメソッドによって返されるオブジェクトが値によってコピーされます。
create_method は、新たな共有オブジェクトを作成するためにサーバプロセスへ伝えるのに使用されるメソッドを typeid の名前で作成し、そのためのプロキシを返すかを決定します。デフォルトでは True です。
BaseManager インスタンスも読み取り専用属性を1つ持っています。
マネージャが使用するアドレスです。
プロセス間の同期のために使用される BaseManager のサブクラスです。 multiprocessing.Manager() はこの型のオブジェクトを返します。
また共有リストやディクショナリの作成もサポートします。
共有 threading.BoundedSemaphore オブジェクトを作成して、そのプロキシを返します。
共有 threading.Condition オブジェクトを作成して、そのプロキシを返します。
lock が提供される場合 threading.Lock か threading.RLock オブジェクトのためのプロキシになります。
共有 threading.Event オブジェクトを作成して、そのプロキシを返します。
共有 threading.Lock オブジェクトを作成して、そのプロキシを返します。
共有 Queue.Queue オブジェクトを作成して、そのプロキシを返します。
共有 threading.RLock オブジェクトを作成して、そのプロキシを返します。
共有 threading.Semaphore オブジェクトを作成して、そのプロキシを返します。
配列を作成して、そのプロキシを返します。
書き込み可能な value 属性を作成して、そのプロキシを返します。
共有 dict オブジェクトを作成して、そのプロキシを返します。
共有 list オブジェクトを作成して、そのプロキシを返します。
ノート
プロキシには、ミュータブルな値や、辞書とリストのプロキシの要素が、いつ変えられたのかを知る方法がないため、これらの値や要素の変化はマネージャを通して伝播しません。要素などを変化させるために、コンテナプロキシに変化されたオブジェクトを再代入できます。
# create a list proxy and append a mutable object (a dictionary) lproxy = manager.list() lproxy.append({}) # now mutate the dictionary d = lproxy[0] d[‘a’] = 1 d[‘b’] = 2 # at this point, the changes to d are not yet synced, but by # reassigning the dictionary, the proxy is notified of the change lproxy[0] = d
Namespace オブジェクトはプライベートなメソッドを持っていますが、書き込み属性を持ちます。そのオブジェクト表現はその属性の値を表示します。
しかし、Namespace オブジェクトのためにプロキシを使用するとき '_' が先頭に付く属性はプロキシの属性になり、参照対象の属性にはなりません。
>>> manager = multiprocessing.Manager()
>>> Global = manager.Namespace()
>>> Global.x = 10
>>> Global.y = 'hello'
>>> Global._z = 12.3 # これはプロキシの属性です
>>> print Global
Namespace(x=10, y='hello')
独自のマネージャを作成するために BaseManager のサブクラスを作成して、マネージャクラスで呼び出し可能なオブジェクトか新たな型を登録するために register() クラスメソッドを使用します。
from multiprocessing.managers import BaseManager
class MathsClass(object):
def add(self, x, y):
return x + y
def mul(self, x, y):
return x * y
class MyManager(BaseManager):
pass
MyManager.register('Maths', MathsClass)
if __name__ == '__main__':
manager = MyManager()
manager.start()
maths = manager.Maths()
print maths.add(4, 3) # 7 を表示
print maths.mul(7, 8) # 56 を表示
あるマシン上でマネージャサーバを実行して、他のマシンからそのサーバを使用するクライアントを持つことができます(ファイアウォールを通過できることが前提)。
次のコマンドを実行することでリモートクライアントからアクセスを受け付ける1つの共有キューのためにサーバを作成します。
>>> from multiprocessing.managers import BaseManager
>>> import Queue
>>> queue = Queue.Queue()
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue', callable=lambda:queue)
>>> m = QueueManager(address=('', 50000), authkey='abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()
あるクライアントからサーバへのアクセスは次のようになります。
>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey='abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.put('hello')
別のクライアントもそれを使用することができます。
>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey='abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.get()
'hello'
ローカルプロセスもそのキューへアクセスすることができます。クライアント上で上述のコードを使用してアクセスします。
>>> from multiprocessing import Process, Queue
>>> from multiprocessing.managers import BaseManager
>>> class Worker(Process):
... def __init__(self, q):
... self.q = q
... super(Worker, self).__init__()
... def run(self):
... self.q.put('local hello')
...
>>> queue = Queue()
>>> w = Worker(queue)
>>> w.start()
>>> class QueueManager(BaseManager): pass
...
>>> QueueManager.register('get_queue', callable=lambda: queue)
>>> m = QueueManager(address=('', 50000), authkey='abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()
プロキシは別のプロセスで(おそらく)有効な共有オブジェクトを 参照する オブジェクトです。共有オブジェクトはプロキシの 参照対象 になると言うことができます。複数のプロキシオブジェクトが同じ参照対象を持つ可能性もあります。
プロキシオブジェクトはその参照対象が持つ対応メソッドを実行するメソッドを持ちます。 (そうは言っても、参照対象の全てのメソッドが必ずしもプロキシ経由で利用可能ではありません) プロキシは通常その参照対象ができることと同じ方法で使用されます。
>>> from multiprocessing import Manager
>>> manager = Manager()
>>> l = manager.list([i*i for i in range(10)])
>>> print l
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> print repr(l)
<ListProxy object, typeid 'list' at 0x...>
>>> l[4]
16
>>> l[2:5]
[4, 9, 16]
プロキシに str() を適用すると参照対象のオブジェクト表現を返すのに対して、 repr() を適用するとプロキシのオブジェクト表現を返すことに注意してください。
プロキシオブジェクトの重要な機能はプロセス間で受け渡し可能な pickle 化ができることです。しかし、プロキシが対応するマネージャプロセスに対して送信される場合、そのプロキシを unpickle するとその参照対象を生成することを覚えておいてください。例えば、これはある共有オブジェクトに別の共有オブジェクトが含められることを意味します。
>>> a = manager.list()
>>> b = manager.list()
>>> a.append(b) # a の参照対象に b の参照対象を含める
>>> print a, b
[[]] []
>>> b.append('hello')
>>> print a, b
[['hello']] ['hello']
ノート
multiprocessing のプロキシ型は値による比較に対して何もサポートしません。そのため、インスタンスでは、
>>> manager.list([1,2,3]) == [1,2,3]
False
比較を行いたいときは参照対象のコピーを使用してください。
プロキシオブジェクトは BaseProxy のサブクラスのインスタンスです。
プロキシの参照対象のメソッドの実行結果を返します。
proxy がプロキシで、プロキシ内の参照対象が obj なら、
proxy._callmethod(methodname, args, kwds)
は、
getattr(obj, methodname)(*args, **kwds)
マネージャプロセス内のこの式を評価します。
返される値はその呼び出し結果のコピーか、新たな共有オブジェクトに対するプロキシになります。詳細は BaseManager.register() の method_to_typeid 引数のドキュメントを参照してください。
例外がその呼び出しによって発生する場合 _callmethod() によって再発生させます。もし他の例外がマネージャプロセスで発生するなら、 RemoteError 例外に変換されて _callmethod() によって発生させます。
特に methodname が 公開 されていない場合は例外が発生することに注意してください。
_callmethod() の使用例になります。
>>> l = manager.list(range(10))
>>> l._callmethod('__len__')
10
>>> l._callmethod('__getslice__', (2, 7)) # `l[2:7]` と等価
[2, 3, 4, 5, 6]
>>> l._callmethod('__getitem__', (20,)) # `l[20]` と等価
Traceback (most recent call last):
...
IndexError: list index out of range
参照対象のコピーを返します。
参照対象が unpickle 化できるなら例外を発生します。
プロキシオブジェクトのオブジェクト表現を返します。
参照対象のオブジェクト表現を返します。
プロキシオブジェクトは弱参照(weakref)コールバックを使用します。プロキシオブジェクトがガベージコレクトされるときにその参照対象が所有するマネージャからその登録を取り消せるようにするためです。
共有オブジェクトはプロキシが参照しなくなったときにマネージャプロセスから削除されます。
Pool クラスでタスクを実行するプロセスのプールを作成することができます。
プロセスプールオブジェクトはジョブが実行されるようにワーカープロセスのプールを制御します。タイムアウトやコールバックで非同期の実行をサポートして、並列 map 実装を持ちます。
processes は使用するワーカープロセスの数です。 processes が None の場合 cpu_count() が返す数を使用します。 initializer が None の場合、各ワーカープロセスが開始時に initializer(*initargs) を呼び出します。
バージョン 2.7 で追加: maxtasksperchild は、使われないリソースの開放のために ワーカープロセスが退出して、新しい ワーカープロセスで置き換えられるまでに完了できるタスクの数です。 デフォルトの maxtasksperchild は None で、ワーカープロセスが プールと同じだけ残ることを意味します。
ノート
Pool 中のワーカープロセスは典型的に、プールのワークキューの存続期間とちょうど同じだけ残ります。(Apache, mod_wsgi, 等のような) 他のシステムによく見られるパターンは、ワーカーが設定された量だけのワークを完了させるまでプールに置いたら退出させ、新しいプロセスが産まれて古いプロセスを置き換えるものです。 Pool の maxtasksperchild 引数は、この能力をエンドユーザに開放します。
apply() 組み込み関数と同じです。その結果を返せるようになるまでブロックします。これらのブロックが与えられる場合、 apply_async() の方がうまく並列実行を処理します。さらに関数はプールの中の一つのワーカーにのみ渡されます。
apply() メソッドの一種で結果オブジェクトを返します。
callback が指定された場合、1つの引数を受け取って呼び出されます。その結果を返せるようになったときに callback が結果オブジェクトに対して (その呼び出しが失敗しない限り)適用されます。その結果を扱う別スレッドはブロックされるので callback はすぐに終了します。
並列な map() 組み込み関数と同じです( iterable な引数を1つだけサポートします)。その結果を返せるようになるまでブロックします。
このメソッドは独立したタスクのようにプロセスプールに対して実行するチャンク数に分割します。チャンク(概算)サイズは chunksize に正の整数を指定することで行います。
map() メソッドの一種で結果オブジェクトを返します。
callback が指定された場合、1つの引数を受け取って呼び出されます。その結果を返せるようになったときに callback が結果オブジェクトに対して (その呼び出しが失敗しない限り)適用されます。その結果を扱う別スレッドはブロックされるので callback はすぐに終了します。
itertools.imap() と同じです。
chunksize 引数は map() メソッドで使用されるものと同じです。引数 iterable がとても大きいなら chunksize に大きな値を指定して使用する方がデフォルト値の 1 を使用するよりもジョブの完了が かなり 速くなります。
また chunksize が 1 の場合 imap() メソッドが返すイテレータの next() メソッドはオプションで timeout パラメータを持ちます。 next(timeout) は、その結果が timeout 秒以内に返されないときに multiprocessing.TimeoutError を発生させます。
イテレータが返す結果の順番が任意の順番で良いと見なされることを除けば imap() と同じです。 (ワーカープロセスが1つしかない場合のみ “正しい” 順番になることが保証されます。)
これ以上プールでタスクが実行されないようにします。全てのタスクが完了した後でワーカープロセスが終了します。
実行中の処理を完了させずにワーカープロセスをすぐに停止します。プールオブジェクトがガベージコレクトされるときに terminate() が呼び出されます。
ワーカープロセスが終了するのを待ちます。 join() を使用する前に close() か terminate() を呼び出さなければなりません。
Pool.apply_async() や Pool.map_async() で返される結果のクラスです。
結果を受け取ったときに返します。 timeout が None ではなくて、その結果が timeout 秒以内に受け取れない場合 multiprocessing.TimeoutError が発生します。リモートの呼び出しが例外を発生させる場合、その例外は get() が再発生させます。
その結果が有効になるか timeout 秒経つまで待ちます。
その呼び出しが完了しているかどうかを返します。
その呼び出しが例外を発生させることなく完了したかどうかを返します。その結果が返せる状態でない場合 AssertionError が発生します。
次の例はプールの使用例を紹介します。
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
pool = Pool(processes=4) # 4つのワーカープロセスで開始
result = pool.apply_async(f, (10,)) # 非同期で "f(10)" を評価
print result.get(timeout=1) # あなたのコンピュータが *かなり* 遅くない限りは "100" を表示
print pool.map(f, range(10)) # "[0, 1, 4,..., 81]" を表示
it = pool.imap(f, range(10))
print it.next() # "0" を表示
print it.next() # "1" を表示
print it.next(timeout=1) # あなたのコンピュータが *かなり* 遅くない限りは "4" を表示
import time
result = pool.apply_async(time.sleep, (10,))
print result.get(timeout=1) # TimeoutError を発生
通常、プロセス間でメッセージを渡すにはキューを使用するか Pipe() が返す Connection オブジェクトを使用します。
しかし multiprocessing.connection モジュールはさらに柔軟な仕組みがあります。基本的にはソケットもしくは Windows の名前付きパイプを扱う高レベルのメッセージ指向 API を提供して hmac モジュールを使用して ダイジェスト認証 もサポートします。
ランダム生成したメッセージをコネクションの相手側へ送信して応答を待ちます。
その応答がキーとして authkey を使用するメッセージのダイジェストと一致する場合、コネクションの相手側へ歓迎メッセージを送信します。そうでなければ AuthenticationError を発生させます。
メッセージを受信して、そのキーとして authkey を使用するメッセージのダイジェストを計算し、ダイジェストを送り返します。
歓迎メッセージを受け取れない場合 AuthenticationError が発生します。
address で渡したアドレスを使用するリスナーに対してコネクションを確立しようとして Connection を返します。
コネクション種別は family 引数で決定しますが、一般的には address のフォーマットから推測できるので、これは指定されません。 ( アドレスフォーマット を参照してください)
authenticate が True か authkey が文字列の場合、ダイジェスト認証が使用されます。認証に使用されるキーは authkey 、又は authkey が None の場合は current_process().authkey のどちらかです。認証が失敗した場合 AuthenticationError が発生します。 認証キー を参照してください。
コネクションを ‘待ち受ける’ 束縛されたソケットか Windows の名前付きパイプのラッパです。
address はリスナーオブジェクトの束縛されたソケットか名前付きパイプが使用するアドレスです。
ノート
‘0.0.0.0’ のアドレスを使用する場合、Windows 上の終点へ接続することができません。終点へ接続したい場合は ‘127.0.0.1’ を使用すべきです。
family は使用するソケット(名前付きパイプ)の種別です。これは 'AF_INET' (TCP ソケット), 'AF_UNIX' (Unix ドメインソケット) 又は 'AF_PIPE' (Windows 名前付きパイプ) という文字列のどれか1つになります。これらのうち 'AF_INET' のみが利用可能であることが保証されています。 family が None の場合 address のフォーマットから推測されたものが使用されます。 address も None の場合はデフォルトが選択されます。詳細は アドレスフォーマット を参照してください。 family が 'AF_UNIX' で address が None の場合 tempfile.mkstemp() を使用して作成されたプライベートな一時ディレクトリにソケットが作成されます。
リスナーオブジェクトがソケットを使用する場合、ソケットに束縛されるときに backlog (デフォルトでは1つ) がソケットの listen() メソッドに対して渡されます。
authenticate が True (デフォルトでは False ) か authkey が None ではない場合、ダイジェスト認証が使用されます。
authkey が文字列の場合、認証キーとして使用されます。そうでない場合は None でなければいけません。
authkey が None 且つ authenticate が True の場合 current_process().authkey が認証キーとして使用されます。 authkey が None 且つ authentication が False の場合、認証は行われません。もし認証が失敗した場合 AuthenticationError が発生します。詳細 認証キー を参照してください。
リスナーオブジェクトの名前付きパイプか束縛されたソケット上でコネクションを受け付けて Connection オブジェクトを返します。認証が失敗した場合 AuthenticationError が発生します。
リスナーオブジェクトの名前付きパイプか束縛されたソケットをクローズします。これはリスナーがガベージコレクトされるときに自動的に呼ばれます。そうは言っても、明示的に close() を呼び出す方が望ましいです。
リスナーオブジェクトは次の読み取り専用属性を持っています。
リスナーオブジェクトが使用中のアドレスです。
最後にコネクションを受け付けたアドレスです。有効なアドレスがない場合は None になります。
このモジュールは2つの例外を定義します。
認証エラーが起こったときに例外が発生します。
例
次のサーバコードは認証キーとして 'secret password' を使用するリスナーを作成します。このサーバはコネクションを待ってクライアントへデータを送信します。
from multiprocessing.connection import Listener
from array import array
address = ('localhost', 6000) # family is deduced to be 'AF_INET'
listener = Listener(address, authkey='secret password')
conn = listener.accept()
print 'connection accepted from', listener.last_accepted
conn.send([2.25, None, 'junk', float])
conn.send_bytes('hello')
conn.send_bytes(array('i', [42, 1729]))
conn.close()
listener.close()
次のコードはサーバへ接続して、サーバからデータを受信します。
from multiprocessing.connection import Client
from array import array
address = ('localhost', 6000)
conn = Client(address, authkey='secret password')
print conn.recv() # => [2.25, None, 'junk', float]
print conn.recv_bytes() # => 'hello'
arr = array('i', [0, 0, 0, 0, 0])
print conn.recv_bytes_into(arr) # => 8
print arr # => array('i', [42, 1729, 0, 0, 0])
conn.close()
デフォルトでは、2つのバックスラッシュで始まる文字列は 'AF_UNIX' よりも 'AF_PIPE' として推測されることに注意してください。
Connection.recv() を使用するとき、データは自動的に unpickle されて受信します。信頼できない接続元からのデータを unpickle することはセキュリティリスクがあります。そのため Listener や Client() はダイジェスト認証を提供するために hmac モジュールを使用します。
認証キーはパスワードとして見なされる文字列です。コネクションが確立すると、双方の終点で正しい接続先であることを証明するために知っているお互いの認証キーを要求します。 (双方の終点が同じキーを使用して通信しようとしても、コネクション上でそのキーを送信することは できません 。)
認証が要求されて認証キーが指定されている場合 current_process().authkey の返す値が使用されます。 (詳細は Process を参照してください。) この値はカレントプロセスを作成する Process オブジェクトによって自動的に継承されます。これは(デフォルトでは)複数プロセスのプログラムの全プロセスが相互にコネクションを確立するときに使用される1つの認証キーを共有することを意味します。
適当な認証キーを os.urandom() を使用して生成することもできます。
ロギングのために幾つかの機能が利用可能です。しかし logging パッケージは、 (ハンドラ種別に依存して)違うプロセスからのメッセージがごちゃ混ぜになるので、プロセスの共有ロックを使用しないことに注意してください。
multiprocessing が使用するロガーを返します。必要に応じて新たなロガーを作成します。
最初に作成するとき、ロガーはレベルに logging.NOTSET が設定されていてデフォルトハンドラがありません。このロガーへ送られるメッセージはデフォルトではルートロガーへ伝播されません。
Windows 上では子プロセスが親プロセスのロガーレベルを継承しないことに注意してください。さらにその他のロガーのカスタマイズ内容も全て継承されません。
この関数は get_logger() に対する呼び出しを実行しますが、 get_logger によって作成されるロガーを返すことに加えて、 '[%(levelname)s/%(processName)s] %(message)s' のフォーマットを使用して sys.stderr へ出力を送るハンドラを追加します。
以下にロギングを有効にした例を紹介します。
>>> import multiprocessing, logging
>>> logger = multiprocessing.log_to_stderr()
>>> logger.setLevel(logging.INFO)
>>> logger.warning('doomed')
[WARNING/MainProcess] doomed
>>> m = multiprocessing.Manager()
[INFO/SyncManager-...] child process calling self.run()
[INFO/SyncManager-...] created temp directory /.../pymp-...
[INFO/SyncManager-...] manager serving at '/.../listener-...'
>>> del m
[INFO/MainProcess] sending shutdown message to manager
[INFO/SyncManager-...] manager exiting with exitcode 0
これらの2つのロギング関数があることに加えて、 multiprocessing モジュールも2つの追加ロギングレベル属性を提供します。それは SUBWARNING と SUBDEBUG です。次の表は通常のレベル階層にうまく適合していることを表します。
Level | Numeric value |
---|---|
SUBWARNING | 25 |
SUBDEBUG | 5 |
完全なロギングレベルの表については logging モジュールを参照してください。
こういった追加のロギングレベルは主に multiprocessing モジュールの信頼できるデバッグメッセージのために使用されます。以下に上述の例に SUBDEBUG を有効にしたものを紹介します。
>>> import multiprocessing, logging
>>> logger = multiprocessing.log_to_stderr()
>>> logger.setLevel(multiprocessing.SUBDEBUG)
>>> logger.warning('doomed')
[WARNING/MainProcess] doomed
>>> m = multiprocessing.Manager()
[INFO/SyncManager-...] child process calling self.run()
[INFO/SyncManager-...] created temp directory /.../pymp-...
[INFO/SyncManager-...] manager serving at '/.../pymp-.../listener-...'
>>> del m
[SUBDEBUG/MainProcess] finalizer calling ...
[INFO/MainProcess] sending shutdown message to manager
[DEBUG/SyncManager-1] manager received shutdown message
[SUBDEBUG/SyncManager-...] calling <Finalize object, callback=unlink, ...
[SUBDEBUG/SyncManager-...] finalizer calling <built-in function unlink> ...
[SUBDEBUG/SyncManager-...] calling <Finalize object, dead>
[SUBDEBUG/SyncManager-...] finalizer calling <function rmtree at 0x5aa730> ...
[INFO/SyncManager-...] manager exiting with exitcode 0
multiprocessing.dummy は multiprocessing の API を複製しますが threading モジュールのラッパーでしかありません。
multiprocessing を使用するときに守るべき確かなガイドラインとイディオムです。
共有状態を避ける
できるだけプロセス間で巨大なデータを移動することは避けるようにすべきです。
threading モジュールのプリミティブな低レベルの同期を使用するよりも、キューかパイプをプロセス間通信に使用することがおそらく最善の方法です。
pickle 機能
プロキシのメソッドへの引数は pickle 化できることを保証します。
プロキシのスレッドセーフ
プロキシオブジェクトをロックで保護しない限り1つ以上のスレッドから使用してはいけません。
(違うプロセスで 同じ プロキシを使用することは問題ではありません。)
ゾンビプロセスを join する
Unix 上ではプロセスが終了したときに join しないと、そのプロセスはゾンビになります。新たなプロセスが開始する(又は active_children() が呼ばれる)ときに、 join されていない全ての完了プロセスが join されるので、あまり多くにはならないでしょう。また、終了したプロセスの Process.is_alive() はそのプロセスを join します。そうは言っても、自分で開始した全てのプロセスを明示的に join することはおそらく良いプラクティスです。
pickle/unpickle より継承する方が良い
Windows 上では multiprocessing の多くの型を子プロセスが使用するために pickle 化する必要があります。しかし、パイプやキューを使用する他のプロセスへ共有オブジェクトを送ることは一般的に避けるべきです。その代わり、どこかに作成された共有リソースへアクセスが必要なプロセスは他のプロセスから継承できるようにそのプログラムを修正すべきです。
プロセスを強制終了させることを避ける
あるプロセスを停止するために Process.terminate() メソッドを使用すると、そのプロセスが現在使用されている(ロック、セマフォ、パイプやキューのような)共有リソースを破壊したり他のプロセスから利用できない状態を引き起こし易いです。
そのため、共有リソースを使用しないプロセスでのみ Process.terminate() を使用するように考慮することがおそらく最善の方法です。
キューを使用するプロセスを join する
キューに要素を追加するプロセスは、全てのバッファされた要素が “feeder” スレッドによって下位層のパイプに対してフィードされるまで終了を待つということを覚えておいてください。 (子プロセスはこの動作を避けるためにキューの Queue.cancel_join_thread() メソッドを呼ぶことができます。)
これはキューを使用するときに、キューに追加された全ての要素が最終的にそのプロセスが join される前に削除されていることを確認する必要があることを意味します。そうしないと、そのキューに要素が追加したプロセスの終了を保証できません。デーモンではないプロセスは自動的に join されることも覚えておいてください。
次の例はデッドロックを引き起こします。
from multiprocessing import Process, Queue def f(q): q.put('X' * 1000000) if __name__ == '__main__': queue = Queue() p = Process(target=f, args=(queue,)) p.start() p.join() # これはデッドロックします obj = queue.get()修正するには最後の2行を入れ替えます(または単純に p.join() の行を削除します)。
明示的に子プロセスへリソースを渡す
Unix 上では子プロセスはグローバルなリソースを使用する親プロセスが作成した共有リソースを使用することができます。しかし、引数としてそのオブジェクトを子プロセスのコンストラクタへ渡す方が良いです。
(潜在的に) Windows 互換なコードを作成することは別として、さらにこれは子プロセスが生き続ける限り、そのオブジェクトは親プロセスでガベージコレクトされないことも保証します。これは親プロセスでそのオブジェクトがガベージコレクトされるときにリソースが開放される場合に重要になるでしょう。
そのため、例えば、
from multiprocessing import Process, Lock def f(): ... do something using "lock" ... if __name__ == '__main__': lock = Lock() for i in range(10): Process(target=f).start()次のように書き直すべきです。
from multiprocessing import Process, Lock def f(l): ... do something using "l" ... if __name__ == '__main__': lock = Lock() for i in range(10): Process(target=f, args=(lock,)).start()
sys.stdin をファイル形式のオブジェクトへ置き換えることに注意してください
muliprocessing は元々 muliprocessing.Process.__bootstrap() の中で無条件に
os.close(sys.stdin.fileno())を呼び出していました — これはプロセス間で問題が起こしてしまいます。そこで、これは以下のように変更されました。
sys.stdin.close() sys.stdin = open(os.devnull)これによってプロセス間同士が衝突して bad file descripter エラーを起こすという基本的な問題は解決しました、しかし、アプリケーションの出力バッファーを sys.stdin() からファイル形式のオブジェクトに置き換えるという潜在的危険を持ち込みます。複数のプロセスがファイル形式オブジェクトの close() を呼び出した場合、オブジェクトに同じデータが何度もフラッシュされ、衝突が起きる危険があります。
もし、ファイル形式オブジェクトを書いて、独自のキャッシュ実装する場合、キャッシュする時に常に pid を記録しておく、 pid が変わったらキュッシュを捨てることでフォークセーフにできます。例:
@property def cache(self): pid = os.getpid() if pid != self._pid: self._pid = pid self._cache = [] return self._cacheより詳しい情報は issue 5155 、 issue 5351 、 issue 5331 を見てください。
Windows では os.fork() がないので幾つか追加制限があります。
さらなる pickle 機能
Process.__init__() へ渡す全ての引数は pickle 化できることを保証します。これは特に束縛、又は非束縛メソッドが Windows 上の target 引数として直接的に使用できないことを意味します。その代わり、まさに関数を定義してください。
また Process をサブクラス化する場合、そのインスタンスが Process.start() メソッドが呼ばれたときに pickle 化できることを保証します。
グローバル変数
子プロセスで実行されるコードがグローバル変数にアクセスしようとする場合、子プロセスが見るその値は Process.start() が呼ばれたときの親プロセスのその値と同じではない可能性があります。
しかし、単にモジュールレベルの定数であるグローバル変数なら問題にはなりません。
メインモジュールの安全なインポート
新たに Python インタプリタによって、意図しない副作用(新たなプロセスを開始する等) を起こさずにメインモジュールを安全にインポートできることを保証します。
例えば Windows で次のモジュールを実行しようとすると RuntimeError で失敗します。
from multiprocessing import Process def foo(): print 'hello' p = Process(target=foo) p.start()代わりに、次のように if __name__ == '__main__': を使用してプログラムの “エントリポイント” を保護すべきです。
from multiprocessing import Process, freeze_support def foo(): print 'hello' if __name__ == '__main__': freeze_support() p = Process(target=foo) p.start()( freeze_support() 行はプログラムが固まらずに実行されるなら通常は取り除かれます。)
これは新たに生成された Python インタプリタがそのモジュールを安全にインポートして、モジュールの foo() 関数を実行します。
プール又はマネージャがメインモジュールで作成される場合に似たような制限が適用されます。
カスタマイズされたマネージャやプロキシの作成方法と使用方法を紹介します。
#
# This module shows how to use arbitrary callables with a subclass of
# `BaseManager`.
#
from multiprocessing import freeze_support
from multiprocessing.managers import BaseManager, BaseProxy
import operator
##
class Foo(object):
def f(self):
print 'you called Foo.f()'
def g(self):
print 'you called Foo.g()'
def _h(self):
print 'you called Foo._h()'
# A simple generator function
def baz():
for i in xrange(10):
yield i*i
# Proxy type for generator objects
class GeneratorProxy(BaseProxy):
_exposed_ = ('next', '__next__')
def __iter__(self):
return self
def next(self):
return self._callmethod('next')
def __next__(self):
return self._callmethod('__next__')
# Function to return the operator module
def get_operator_module():
return operator
##
class MyManager(BaseManager):
pass
# register the Foo class; make `f()` and `g()` accessible via proxy
MyManager.register('Foo1', Foo)
# register the Foo class; make `g()` and `_h()` accessible via proxy
MyManager.register('Foo2', Foo, exposed=('g', '_h'))
# register the generator function baz; use `GeneratorProxy` to make proxies
MyManager.register('baz', baz, proxytype=GeneratorProxy)
# register get_operator_module(); make public functions accessible via proxy
MyManager.register('operator', get_operator_module)
##
def test():
manager = MyManager()
manager.start()
print '-' * 20
f1 = manager.Foo1()
f1.f()
f1.g()
assert not hasattr(f1, '_h')
assert sorted(f1._exposed_) == sorted(['f', 'g'])
print '-' * 20
f2 = manager.Foo2()
f2.g()
f2._h()
assert not hasattr(f2, 'f')
assert sorted(f2._exposed_) == sorted(['g', '_h'])
print '-' * 20
it = manager.baz()
for i in it:
print '<%d>' % i,
print
print '-' * 20
op = manager.operator()
print 'op.add(23, 45) =', op.add(23, 45)
print 'op.pow(2, 94) =', op.pow(2, 94)
print 'op.getslice(range(10), 2, 6) =', op.getslice(range(10), 2, 6)
print 'op.repeat(range(5), 3) =', op.repeat(range(5), 3)
print 'op._exposed_ =', op._exposed_
##
if __name__ == '__main__':
freeze_support()
test()
Pool の使用例を紹介します。
#
# A test of `multiprocessing.Pool` class
#
import multiprocessing
import time
import random
import sys
#
# Functions used by test code
#
def calculate(func, args):
result = func(*args)
return '%s says that %s%s = %s' % (
multiprocessing.current_process().name,
func.__name__, args, result
)
def calculatestar(args):
return calculate(*args)
def mul(a, b):
time.sleep(0.5*random.random())
return a * b
def plus(a, b):
time.sleep(0.5*random.random())
return a + b
def f(x):
return 1.0 / (x-5.0)
def pow3(x):
return x**3
def noop(x):
pass
#
# Test code
#
def test():
print 'cpu_count() = %d\n' % multiprocessing.cpu_count()
#
# Create pool
#
PROCESSES = 4
print 'Creating pool with %d processes\n' % PROCESSES
pool = multiprocessing.Pool(PROCESSES)
print 'pool = %s' % pool
print
#
# Tests
#
TASKS = [(mul, (i, 7)) for i in range(10)] + \
[(plus, (i, 8)) for i in range(10)]
results = [pool.apply_async(calculate, t) for t in TASKS]
imap_it = pool.imap(calculatestar, TASKS)
imap_unordered_it = pool.imap_unordered(calculatestar, TASKS)
print 'Ordered results using pool.apply_async():'
for r in results:
print '\t', r.get()
print
print 'Ordered results using pool.imap():'
for x in imap_it:
print '\t', x
print
print 'Unordered results using pool.imap_unordered():'
for x in imap_unordered_it:
print '\t', x
print
print 'Ordered results using pool.map() --- will block till complete:'
for x in pool.map(calculatestar, TASKS):
print '\t', x
print
#
# Simple benchmarks
#
N = 100000
print 'def pow3(x): return x**3'
t = time.time()
A = map(pow3, xrange(N))
print '\tmap(pow3, xrange(%d)):\n\t\t%s seconds' % \
(N, time.time() - t)
t = time.time()
B = pool.map(pow3, xrange(N))
print '\tpool.map(pow3, xrange(%d)):\n\t\t%s seconds' % \
(N, time.time() - t)
t = time.time()
C = list(pool.imap(pow3, xrange(N), chunksize=N//8))
print '\tlist(pool.imap(pow3, xrange(%d), chunksize=%d)):\n\t\t%s' \
' seconds' % (N, N//8, time.time() - t)
assert A == B == C, (len(A), len(B), len(C))
print
L = [None] * 1000000
print 'def noop(x): pass'
print 'L = [None] * 1000000'
t = time.time()
A = map(noop, L)
print '\tmap(noop, L):\n\t\t%s seconds' % \
(time.time() - t)
t = time.time()
B = pool.map(noop, L)
print '\tpool.map(noop, L):\n\t\t%s seconds' % \
(time.time() - t)
t = time.time()
C = list(pool.imap(noop, L, chunksize=len(L)//8))
print '\tlist(pool.imap(noop, L, chunksize=%d)):\n\t\t%s seconds' % \
(len(L)//8, time.time() - t)
assert A == B == C, (len(A), len(B), len(C))
print
del A, B, C, L
#
# Test error handling
#
print 'Testing error handling:'
try:
print pool.apply(f, (5,))
except ZeroDivisionError:
print '\tGot ZeroDivisionError as expected from pool.apply()'
else:
raise AssertionError, 'expected ZeroDivisionError'
try:
print pool.map(f, range(10))
except ZeroDivisionError:
print '\tGot ZeroDivisionError as expected from pool.map()'
else:
raise AssertionError, 'expected ZeroDivisionError'
try:
print list(pool.imap(f, range(10)))
except ZeroDivisionError:
print '\tGot ZeroDivisionError as expected from list(pool.imap())'
else:
raise AssertionError, 'expected ZeroDivisionError'
it = pool.imap(f, range(10))
for i in range(10):
try:
x = it.next()
except ZeroDivisionError:
if i == 5:
pass
except StopIteration:
break
else:
if i == 5:
raise AssertionError, 'expected ZeroDivisionError'
assert i == 9
print '\tGot ZeroDivisionError as expected from IMapIterator.next()'
print
#
# Testing timeouts
#
print 'Testing ApplyResult.get() with timeout:',
res = pool.apply_async(calculate, TASKS[0])
while 1:
sys.stdout.flush()
try:
sys.stdout.write('\n\t%s' % res.get(0.02))
break
except multiprocessing.TimeoutError:
sys.stdout.write('.')
print
print
print 'Testing IMapIterator.next() with timeout:',
it = pool.imap(calculatestar, TASKS)
while 1:
sys.stdout.flush()
try:
sys.stdout.write('\n\t%s' % it.next(0.02))
except StopIteration:
break
except multiprocessing.TimeoutError:
sys.stdout.write('.')
print
print
#
# Testing callback
#
print 'Testing callback:'
A = []
B = [56, 0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
r = pool.apply_async(mul, (7, 8), callback=A.append)
r.wait()
r = pool.map_async(pow3, range(10), callback=A.extend)
r.wait()
if A == B:
print '\tcallbacks succeeded\n'
else:
print '\t*** callbacks failed\n\t\t%s != %s\n' % (A, B)
#
# Check there are no outstanding tasks
#
assert not pool._cache, 'cache = %r' % pool._cache
#
# Check close() methods
#
print 'Testing close():'
for worker in pool._pool:
assert worker.is_alive()
result = pool.apply_async(time.sleep, [0.5])
pool.close()
pool.join()
assert result.get() is None
for worker in pool._pool:
assert not worker.is_alive()
print '\tclose() succeeded\n'
#
# Check terminate() method
#
print 'Testing terminate():'
pool = multiprocessing.Pool(2)
DELTA = 0.1
ignore = pool.apply(pow3, [2])
results = [pool.apply_async(time.sleep, [DELTA]) for i in range(100)]
pool.terminate()
pool.join()
for worker in pool._pool:
assert not worker.is_alive()
print '\tterminate() succeeded\n'
#
# Check garbage collection
#
print 'Testing garbage collection:'
pool = multiprocessing.Pool(2)
DELTA = 0.1
processes = pool._pool
ignore = pool.apply(pow3, [2])
results = [pool.apply_async(time.sleep, [DELTA]) for i in range(100)]
results = pool = None
time.sleep(DELTA * 2)
for worker in processes:
assert not worker.is_alive()
print '\tgarbage collection succeeded\n'
if __name__ == '__main__':
multiprocessing.freeze_support()
assert len(sys.argv) in (1, 2)
if len(sys.argv) == 1 or sys.argv[1] == 'processes':
print ' Using processes '.center(79, '-')
elif sys.argv[1] == 'threads':
print ' Using threads '.center(79, '-')
import multiprocessing.dummy as multiprocessing
else:
print 'Usage:\n\t%s [processes | threads]' % sys.argv[0]
raise SystemExit(2)
test()
ロック、コンディションやキューのような同期の例を紹介します。
#
# A test file for the `multiprocessing` package
#
import time, sys, random
from Queue import Empty
import multiprocessing # may get overwritten
#### TEST_VALUE
def value_func(running, mutex):
random.seed()
time.sleep(random.random()*4)
mutex.acquire()
print '\n\t\t\t' + str(multiprocessing.current_process()) + ' has finished'
running.value -= 1
mutex.release()
def test_value():
TASKS = 10
running = multiprocessing.Value('i', TASKS)
mutex = multiprocessing.Lock()
for i in range(TASKS):
p = multiprocessing.Process(target=value_func, args=(running, mutex))
p.start()
while running.value > 0:
time.sleep(0.08)
mutex.acquire()
print running.value,
sys.stdout.flush()
mutex.release()
print
print 'No more running processes'
#### TEST_QUEUE
def queue_func(queue):
for i in range(30):
time.sleep(0.5 * random.random())
queue.put(i*i)
queue.put('STOP')
def test_queue():
q = multiprocessing.Queue()
p = multiprocessing.Process(target=queue_func, args=(q,))
p.start()
o = None
while o != 'STOP':
try:
o = q.get(timeout=0.3)
print o,
sys.stdout.flush()
except Empty:
print 'TIMEOUT'
print
#### TEST_CONDITION
def condition_func(cond):
cond.acquire()
print '\t' + str(cond)
time.sleep(2)
print '\tchild is notifying'
print '\t' + str(cond)
cond.notify()
cond.release()
def test_condition():
cond = multiprocessing.Condition()
p = multiprocessing.Process(target=condition_func, args=(cond,))
print cond
cond.acquire()
print cond
cond.acquire()
print cond
p.start()
print 'main is waiting'
cond.wait()
print 'main has woken up'
print cond
cond.release()
print cond
cond.release()
p.join()
print cond
#### TEST_SEMAPHORE
def semaphore_func(sema, mutex, running):
sema.acquire()
mutex.acquire()
running.value += 1
print running.value, 'tasks are running'
mutex.release()
random.seed()
time.sleep(random.random()*2)
mutex.acquire()
running.value -= 1
print '%s has finished' % multiprocessing.current_process()
mutex.release()
sema.release()
def test_semaphore():
sema = multiprocessing.Semaphore(3)
mutex = multiprocessing.RLock()
running = multiprocessing.Value('i', 0)
processes = [
multiprocessing.Process(target=semaphore_func,
args=(sema, mutex, running))
for i in range(10)
]
for p in processes:
p.start()
for p in processes:
p.join()
#### TEST_JOIN_TIMEOUT
def join_timeout_func():
print '\tchild sleeping'
time.sleep(5.5)
print '\n\tchild terminating'
def test_join_timeout():
p = multiprocessing.Process(target=join_timeout_func)
p.start()
print 'waiting for process to finish'
while 1:
p.join(timeout=1)
if not p.is_alive():
break
print '.',
sys.stdout.flush()
#### TEST_EVENT
def event_func(event):
print '\t%r is waiting' % multiprocessing.current_process()
event.wait()
print '\t%r has woken up' % multiprocessing.current_process()
def test_event():
event = multiprocessing.Event()
processes = [multiprocessing.Process(target=event_func, args=(event,))
for i in range(5)]
for p in processes:
p.start()
print 'main is sleeping'
time.sleep(2)
print 'main is setting event'
event.set()
for p in processes:
p.join()
#### TEST_SHAREDVALUES
def sharedvalues_func(values, arrays, shared_values, shared_arrays):
for i in range(len(values)):
v = values[i][1]
sv = shared_values[i].value
assert v == sv
for i in range(len(values)):
a = arrays[i][1]
sa = list(shared_arrays[i][:])
assert a == sa
print 'Tests passed'
def test_sharedvalues():
values = [
('i', 10),
('h', -2),
('d', 1.25)
]
arrays = [
('i', range(100)),
('d', [0.25 * i for i in range(100)]),
('H', range(1000))
]
shared_values = [multiprocessing.Value(id, v) for id, v in values]
shared_arrays = [multiprocessing.Array(id, a) for id, a in arrays]
p = multiprocessing.Process(
target=sharedvalues_func,
args=(values, arrays, shared_values, shared_arrays)
)
p.start()
p.join()
assert p.exitcode == 0
####
def test(namespace=multiprocessing):
global multiprocessing
multiprocessing = namespace
for func in [ test_value, test_queue, test_condition,
test_semaphore, test_join_timeout, test_event,
test_sharedvalues ]:
print '\n\t######## %s\n' % func.__name__
func()
ignore = multiprocessing.active_children() # cleanup any old processes
if hasattr(multiprocessing, '_debug_info'):
info = multiprocessing._debug_info()
if info:
print info
raise ValueError, 'there should be no positive refcounts left'
if __name__ == '__main__':
multiprocessing.freeze_support()
assert len(sys.argv) in (1, 2)
if len(sys.argv) == 1 or sys.argv[1] == 'processes':
print ' Using processes '.center(79, '-')
namespace = multiprocessing
elif sys.argv[1] == 'manager':
print ' Using processes and a manager '.center(79, '-')
namespace = multiprocessing.Manager()
namespace.Process = multiprocessing.Process
namespace.current_process = multiprocessing.current_process
namespace.active_children = multiprocessing.active_children
elif sys.argv[1] == 'threads':
print ' Using threads '.center(79, '-')
import multiprocessing.dummy as namespace
else:
print 'Usage:\n\t%s [processes | manager | threads]' % sys.argv[0]
raise SystemExit, 2
test(namespace)
ワーカープロセスのコレクションに対するタスクをフィードするキューの使用方法とその結果をまとめる方法の例をを紹介します。
#
# Simple example which uses a pool of workers to carry out some tasks.
#
# Notice that the results will probably not come out of the output
# queue in the same in the same order as the corresponding tasks were
# put on the input queue. If it is important to get the results back
# in the original order then consider using `Pool.map()` or
# `Pool.imap()` (which will save on the amount of code needed anyway).
#
import time
import random
from multiprocessing import Process, Queue, current_process, freeze_support
#
# Function run by worker processes
#
def worker(input, output):
for func, args in iter(input.get, 'STOP'):
result = calculate(func, args)
output.put(result)
#
# Function used to calculate result
#
def calculate(func, args):
result = func(*args)
return '%s says that %s%s = %s' % \
(current_process().name, func.__name__, args, result)
#
# Functions referenced by tasks
#
def mul(a, b):
time.sleep(0.5*random.random())
return a * b
def plus(a, b):
time.sleep(0.5*random.random())
return a + b
#
#
#
def test():
NUMBER_OF_PROCESSES = 4
TASKS1 = [(mul, (i, 7)) for i in range(20)]
TASKS2 = [(plus, (i, 8)) for i in range(10)]
# Create queues
task_queue = Queue()
done_queue = Queue()
# Submit tasks
for task in TASKS1:
task_queue.put(task)
# Start worker processes
for i in range(NUMBER_OF_PROCESSES):
Process(target=worker, args=(task_queue, done_queue)).start()
# Get and print results
print 'Unordered results:'
for i in range(len(TASKS1)):
print '\t', done_queue.get()
# Add more tasks using `put()`
for task in TASKS2:
task_queue.put(task)
# Get and print some more results
for i in range(len(TASKS2)):
print '\t', done_queue.get()
# Tell child processes to stop
for i in range(NUMBER_OF_PROCESSES):
task_queue.put('STOP')
if __name__ == '__main__':
freeze_support()
test()
ワーカープロセスのプールが1つのソケットを共有してそれぞれの SimpleHTTPServer.HttpServer インスタンスを実行する方法の例を紹介します。
#
# Example where a pool of http servers share a single listening socket
#
# On Windows this module depends on the ability to pickle a socket
# object so that the worker processes can inherit a copy of the server
# object. (We import `multiprocessing.reduction` to enable this pickling.)
#
# Not sure if we should synchronize access to `socket.accept()` method by
# using a process-shared lock -- does not seem to be necessary.
#
import os
import sys
from multiprocessing import Process, current_process, freeze_support
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
if sys.platform == 'win32':
import multiprocessing.reduction # make sockets pickable/inheritable
def note(format, *args):
sys.stderr.write('[%s]\t%s\n' % (current_process().name, format%args))
class RequestHandler(SimpleHTTPRequestHandler):
# we override log_message() to show which process is handling the request
def log_message(self, format, *args):
note(format, *args)
def serve_forever(server):
note('starting server')
try:
server.serve_forever()
except KeyboardInterrupt:
pass
def runpool(address, number_of_processes):
# create a single server object -- children will each inherit a copy
server = HTTPServer(address, RequestHandler)
# create child processes to act as workers
for i in range(number_of_processes-1):
Process(target=serve_forever, args=(server,)).start()
# main process also acts as a worker
serve_forever(server)
def test():
DIR = os.path.join(os.path.dirname(__file__), '..')
ADDRESS = ('localhost', 8000)
NUMBER_OF_PROCESSES = 4
print 'Serving at http://%s:%d using %d worker processes' % \
(ADDRESS[0], ADDRESS[1], NUMBER_OF_PROCESSES)
print 'To exit press Ctrl-' + ['C', 'Break'][sys.platform=='win32']
os.chdir(DIR)
runpool(ADDRESS, NUMBER_OF_PROCESSES)
if __name__ == '__main__':
freeze_support()
test()
multiprocessing と threading を比較した簡単なベンチマークです。
#
# Simple benchmarks for the multiprocessing package
#
import time, sys, multiprocessing, threading, Queue, gc
if sys.platform == 'win32':
_timer = time.clock
else:
_timer = time.time
delta = 1
#### TEST_QUEUESPEED
def queuespeed_func(q, c, iterations):
a = '0' * 256
c.acquire()
c.notify()
c.release()
for i in xrange(iterations):
q.put(a)
q.put('STOP')
def test_queuespeed(Process, q, c):
elapsed = 0
iterations = 1
while elapsed < delta:
iterations *= 2
p = Process(target=queuespeed_func, args=(q, c, iterations))
c.acquire()
p.start()
c.wait()
c.release()
result = None
t = _timer()
while result != 'STOP':
result = q.get()
elapsed = _timer() - t
p.join()
print iterations, 'objects passed through the queue in', elapsed, 'seconds'
print 'average number/sec:', iterations/elapsed
#### TEST_PIPESPEED
def pipe_func(c, cond, iterations):
a = '0' * 256
cond.acquire()
cond.notify()
cond.release()
for i in xrange(iterations):
c.send(a)
c.send('STOP')
def test_pipespeed():
c, d = multiprocessing.Pipe()
cond = multiprocessing.Condition()
elapsed = 0
iterations = 1
while elapsed < delta:
iterations *= 2
p = multiprocessing.Process(target=pipe_func,
args=(d, cond, iterations))
cond.acquire()
p.start()
cond.wait()
cond.release()
result = None
t = _timer()
while result != 'STOP':
result = c.recv()
elapsed = _timer() - t
p.join()
print iterations, 'objects passed through connection in',elapsed,'seconds'
print 'average number/sec:', iterations/elapsed
#### TEST_SEQSPEED
def test_seqspeed(seq):
elapsed = 0
iterations = 1
while elapsed < delta:
iterations *= 2
t = _timer()
for i in xrange(iterations):
a = seq[5]
elapsed = _timer()-t
print iterations, 'iterations in', elapsed, 'seconds'
print 'average number/sec:', iterations/elapsed
#### TEST_LOCK
def test_lockspeed(l):
elapsed = 0
iterations = 1
while elapsed < delta:
iterations *= 2
t = _timer()
for i in xrange(iterations):
l.acquire()
l.release()
elapsed = _timer()-t
print iterations, 'iterations in', elapsed, 'seconds'
print 'average number/sec:', iterations/elapsed
#### TEST_CONDITION
def conditionspeed_func(c, N):
c.acquire()
c.notify()
for i in xrange(N):
c.wait()
c.notify()
c.release()
def test_conditionspeed(Process, c):
elapsed = 0
iterations = 1
while elapsed < delta:
iterations *= 2
c.acquire()
p = Process(target=conditionspeed_func, args=(c, iterations))
p.start()
c.wait()
t = _timer()
for i in xrange(iterations):
c.notify()
c.wait()
elapsed = _timer()-t
c.release()
p.join()
print iterations * 2, 'waits in', elapsed, 'seconds'
print 'average number/sec:', iterations * 2 / elapsed
####
def test():
manager = multiprocessing.Manager()
gc.disable()
print '\n\t######## testing Queue.Queue\n'
test_queuespeed(threading.Thread, Queue.Queue(),
threading.Condition())
print '\n\t######## testing multiprocessing.Queue\n'
test_queuespeed(multiprocessing.Process, multiprocessing.Queue(),
multiprocessing.Condition())
print '\n\t######## testing Queue managed by server process\n'
test_queuespeed(multiprocessing.Process, manager.Queue(),
manager.Condition())
print '\n\t######## testing multiprocessing.Pipe\n'
test_pipespeed()
print
print '\n\t######## testing list\n'
test_seqspeed(range(10))
print '\n\t######## testing list managed by server process\n'
test_seqspeed(manager.list(range(10)))
print '\n\t######## testing Array("i", ..., lock=False)\n'
test_seqspeed(multiprocessing.Array('i', range(10), lock=False))
print '\n\t######## testing Array("i", ..., lock=True)\n'
test_seqspeed(multiprocessing.Array('i', range(10), lock=True))
print
print '\n\t######## testing threading.Lock\n'
test_lockspeed(threading.Lock())
print '\n\t######## testing threading.RLock\n'
test_lockspeed(threading.RLock())
print '\n\t######## testing multiprocessing.Lock\n'
test_lockspeed(multiprocessing.Lock())
print '\n\t######## testing multiprocessing.RLock\n'
test_lockspeed(multiprocessing.RLock())
print '\n\t######## testing lock managed by server process\n'
test_lockspeed(manager.Lock())
print '\n\t######## testing rlock managed by server process\n'
test_lockspeed(manager.RLock())
print
print '\n\t######## testing threading.Condition\n'
test_conditionspeed(threading.Thread, threading.Condition())
print '\n\t######## testing multiprocessing.Condition\n'
test_conditionspeed(multiprocessing.Process, multiprocessing.Condition())
print '\n\t######## testing condition managed by a server process\n'
test_conditionspeed(multiprocessing.Process, manager.Condition())
gc.enable()
if __name__ == '__main__':
multiprocessing.freeze_support()
test()