pythonのmultiprocessingを使ってみた。 〜失敗編〜
自分はスクリプトではpythonが好きだ。処女はBashとDOS Batch Fileで果てたが初めてのインタプリタはCと相性の良いと聞いたPerlにしようかと思ったが、当時のPerlの参考となるスクリプトが汚くてセキュリティ的にもボロボロで、CGIにいたっては開発しにくい上にprint文でhtml出力している部分を見てしまった時には愛想が尽きてしまっていた。
結局、CGIに関してはPHP、スクリプトはPythonに落ち着いたのだがpython3.xが出ても依然とpython2.5を愛用している。ライブラリ依存的な意味でだ。pydが憎い。あぁ、CPythonよ、なぜ柵(さく)と柵(しがらみ)は同じ字なのだろう。
というわけで、最近ではRHEL6/CentOS6ではpythonをアンインストールするとカーネルまでアンインストールされるぐらいOSと密都合しているCPythonですが、JITなPyPyやGIL問題とか気にしてIronPythonに浮気をするついでにVersion2.6以降multiprocessingに触れてみようとおもった。余談だが、IronPythonは起動が重すぎ。
そもそもスレッドとプロセス問題なんてC/C++ with Linuxで散々悩み通った道だ、worker(スレッド)とfork(プロセス)がどうたらこうたらなんて今更感がしなくもないということでpyDoc眺めつつサクっと書いてみたのが以下である。
#! /usr/bin/env python # -*- coding: utf-8 -*- try: import multiprocessing as _threading from multiprocessing import Process as _thread except ImportError: import threading as _threading from threading import Thread as _thread class Async: def __init__(self, concurrent=1): self.lock = _threading.BoundedSemaphore(concurrent) def __call__(self, callBack, errorBack, **kwargs): def deferred(): try: callBack(**kwargs) except Exception, errors: errorBack(errors, callBack, **kwargs) finally: self.lock.release() self.lock.acquire() thread = _thread(target=deferred) thread.start()
自分は関数内で関数を宣言するのは好きだ。匿名関数というかlambda式つかえと言われそうな気もしないが、pythonに至っては違いはないだろう。処理途中の関数宣言はさすがにしませんが…
以上のコードですが、書いてたときは完全無欠と思っていて確かにpython2.5などのthreadで動かすには問題はなかった。ビューティフォー…しかし、いざ2.7で動かそうとおもいきや、
pickle.PicklingError: Can't pickle <function deferred at 0x00EE9CF0>: it's not found as Async.deferred
そうかプロセス化してしまっているからunboundとかのスコープが閉じているんだ。
そうだよな、そうじゃなかったらmultiprocessing.Valueとかmultiprocessing.Arrayとか要らないよな…。
たとえ関数を展開して静的Classとして読んだとしてもlockだけはValue経由で呼ばないといけないだろう。いくらThreadと似ているといっても別物である。あぁ、失敗だ。