いやー,multiprocessingモジュールイイよ。パねえよ。要はプロセス間通信を行うときに便利なパッケージで,threadingと似たようなAPIなのでGILが回避できてマルチプロセッサとかマルチコアの性能を有効に使えてウハウハとか,リモートマシンにあるプロセスと通信したりオブジェクトをやりとりしたりできてウハウハとか,まあそういうモノです。これでおっきしない奴は技術者として大切な資質を失っていると思うよ。べつにおっきしなくてもいいんだけど,そういう奴とは一緒に仕事したくないよ。
ドキュメントのイントロダクションを超訳してみたです。
はじめに
multiprocessingはthreadingモジュールと似たAPIを使ってプロセスを生成するパッケージです。 multiprocessingパッケージはローカルとリモートマシンのプロセス並行制御をサポートしています。スレッドの代わりにプロセスを使うことで,GIL(Global Interpreter Lock)が起こす問題を効果的に避けることができます。multiprocessingモジュールを使うことによって,プログラマはマルチプロセッサの恩恵を十二分に享受できるようになります。このモジュールはUnixとWindowsで動作します。Processクラス
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()
このコードにあるf()という関数は,子プロセスで稼働します。
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() # prints "[42, None, 'hello']"
p.join()
Queueオブジェクトはスレッド/プロセスセーフです。
Pipes
Pipe()関数はパイプで接続した2つの接続オブジェクトを返します。デフォルトでは双方向通信を行うオブジェクトを返します。以下のような使い方をします。
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() # prints "[42, None, 'hello']"
p.join()
Pipe()関数が返す2つのコネクションオブジェクトはパイプの2つの端のようなものです。それぞれのコネクションオブジェクトは反対の端と通信を行うためのsend()とrecv()メソッドを持っています。2つのプロセス(またはスレッド)が同時に,パイプの同じ側で受信,または送信をしようとすると,パイプの中のデータが壊れてしまうことがあることに注意して下さい。もちろん,パイプの両端で送受信をすれば,データが壊れるリスクはありません。
複数プロセス間の同期
multiprocessingはthreadingが持つ同期をするための仕組みを持っています。ひとつのプロセスだけが標準出力に出力することを保証するために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は手法を2つ提供しています。
共有メモリ
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[:]
このコードは,以下のような出力をします。
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
より柔軟に共有メモリーを活用できるように,任意のctypesオブジェクトの生成をサポートするmultiprocessing.sharedctypesを使うこともできます。
サーバプロセス
Manager()が返すmanagerオブジェクトを使うと,Pythonオブジェクトを保持するサーバプロセスをコントロールできます。また,プロキシーを使って他のプロセスを操作できます。Manager()が返すmanagerオブジェクトはリスト型,辞書型,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,ValueやArrayを扱えます。以下がサンプルコードです。
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
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
ワーカプールを使う
Poolクラスはワーカプロセスのプールの役割を果たします。Poolクラスのメソッドを使うと,タスクをワーカプロセスとして振る舞わせることができます。方法はいくつかあります。以下がサンプルコードです。def f(x):
return x*x
if __name__ == '__main__':
pool = Pool(processes=4) # start 4 worker processes
result = pool.applyAsync(f, [10]) # evaluate "f(10)" asynchronously
print result.get(timeout=1) # prints "100" unless your computer is *very* slow
print pool.map(f, range(10)) # prints "[0, 1, 4,..., 81]"