このサイトについて

2.6に新搭載のmultiprocessingを見て俺のPythonがおっきした件

2.6に新搭載のmultiprocessingを見て俺のPythonがおっきした件

いやー,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を使ったりできます。以下がサンプルです。

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は手法を2つ提供しています。

共有メモリ

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]

'd'と'i'という引数はarrayモジュールで使われる型コードです。numとarrオブジェクトの型を指定するために使います。'd'はfloat型,'i'は符号付き整数を示します。共有オブジェクトはスレッド/プロセスセーフになります。
より柔軟に共有メモリーを活用できるように,任意のctypesオブジェクトの生成をサポートするmultiprocessing.sharedctypesを使うこともできます。

サーバプロセス

Manager()が返すmanagerオブジェクトを使うと,Pythonオブジェクトを保持するサーバプロセスをコントロールできます。また,プロキシーを使って他のプロセスを操作できます。
Manager()が返すmanagerオブジェクトはリスト型,辞書型,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]

複数サーバプロセスのmanagerオブジェクトは,共有メモリより柔軟です。なぜなら、managerオブジェクトでは任意のデータ型を利用できるからです。さらに,単体のmanagerオブジェクトは,異なるコンピュータ上にある複数のプロセス上でネットワークを通じて共有できます。ただし,managerオブジェクトは共有メモリよりも遅いのが難点です。

ワーカプールを使う

Poolクラスはワーカプロセスのプールの役割を果たします。Poolクラスのメソッドを使うと,タスクをワーカプロセスとして振る舞わせることができます。方法はいくつかあります。以下がサンプルコードです。

from multiprocessing import 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]"







2010-08-27 04:48