Geventの良さ
ここによく出てくるEvent Loop...
Event LooPってなんだ?
はい、イメージで言うとselectで待つこと。
例えば、
同期的な受信処理を書くと…
sock = socket()
sock.recv() #ここでデータが来るのを待つ
これだと、一つのプロセスはデータが来るのを待ち続けることになる。
ゆえに、非同期的にデータをselectで待ちます。
すると
sock = socket() select([sock],[],[])
という感じになります。
ちなみに、select関数はこのようになっております。
FUNCTIONS select(rlist, wlist, xlist, timeout=None)
Greenletというのは非同期の考え方をより抽象的にしたもので、
ひとつのプロセスで複数のコンテキストを扱えるようになります。
「2つ以上違うプロセスを同時で動かす」というイメージを持っています。
gevent.spawnはバックグラウンドで別のプロセスを動かすというイメージです。
geventが優秀なのは、自分でコンテキストスイッチを制御できるということです。
通常はOSが制御するもんですね。
コンテキストスイッチ (context switch)
とは、複数のプロセスが1つのCPUを共有できるように、CPUの状態(コンテキスト)を保存したり復元したりする過程のことである。 from Wiki
あと、感動的なのが
select関数だとロックしてしまうところ、
geventはその間別のgreenletが動かせるということになります。
#!/usr/bin/python import gevent,random,time start = time.time() middle = 0 def task(pid): gevent.sleep(random.randint(0,2)) print("Task",pid,"End") def syn(): global middle for i in xrange(10): task(i) middle = time.time() - start print(middle) def asyn(): global middle threads = [gevent.spawn(task,i) for i in xrange(0,10)] gevent.joinall(threads) end = time.time() - middle - start print(end) if __name__ == '__main__': print("1:") syn() print("2:") asyn()
実行結果
1: ('Task', 0, 'End') ('Task', 1, 'End') ('Task', 2, 'End') ('Task', 3, 'End') ('Task', 4, 'End') ('Task', 5, 'End') ('Task', 6, 'End') ('Task', 7, 'End') ('Task', 8, 'End') ('Task', 9, 'End') 10.011341095 2: ('Task', 0, 'End') ('Task', 3, 'End') ('Task', 5, 'End') ('Task', 6, 'End') ('Task', 8, 'End') ('Task', 7, 'End') ('Task', 1, 'End') ('Task', 2, 'End') ('Task', 4, 'End') ('Task', 9, 'End') 2.0019159317
公式サイトからパクってきて、自分で少し書き換えました。
非常に感動的ですね。
synとasynで約8秒差もあります。
もっと気になったことを師匠に聞いてみました。
「一番上が親プロセスで、中央の2つが(マルチ)コアで、下段がgevent ライクの
マルチプロセス。これだと、相当効率よく処理ができるんじゃないかと」
聞いてみたところ、難しいと答えられました。
「複数のCPUコアを使いたいなら、それはOSの複数プロセスを使わないといけなくて、その場合multiprocessingを使います
そうですね、それはできると思います(結構複雑になりますが)
geventはmultiprocessingと合わせて使うことを意図されていないと思うので、データの受け渡しや同期の取り方が難しいってことです」
とのことです。
その他に
このプログラムの重要な部分は、与えられた関数を greenlet スレッドの中に ラップする gevent.spawn です。 生成された greenlet のリストが threads 配列に格納され、 gevent.joinall 関数に渡されます。この関数は今のプログラムを、与えられた すべての greenlet を実行するためにブロックします。 すべての greenlet が終了した時に次のステップが実行されます。
と書いてありますが、少し表現が難しいですね。
仮にもできるんだから、やってみたいですね。
何故やりたいか。それは、Scapyのようなstrを生成するのに時間がかかるプロセスを
効率よく分担させたいからです。
いっそうのこと、gevent勉強して、自分でTCPプロトコルスタック書いた方がいいんじゃないか
という気持ちにもかられますが、やっぱり男はファンタジスタです。
夢をもって、Scapyを追いかけたいと思います。
明日ぐらいにはScapyのクラス継承を楽しみたいなと思っております。
もし、geventに興味持ったら、公式サイト閲覧することを勧めます.