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秒差もあります。

もっと気になったことを師匠に聞いてみました。

f:id:reonreon3reon:20140326043539p:plain

「一番上が親プロセスで、中央の2つが(マルチ)コアで、下段がgevent ライクの
マルチプロセス。これだと、相当効率よく処理ができるんじゃないかと」
聞いてみたところ、難しいと答えられました。

「複数のCPUコアを使いたいなら、それはOSの複数プロセスを使わないといけなくて、その場合multiprocessingを使います
そうですね、それはできると思います(結構複雑になりますが)
geventはmultiprocessingと合わせて使うことを意図されていないと思うので、データの受け渡しや同期の取り方が難しいってことです」

とのことです。

その他に

このプログラムの重要な部分は、与えられた関数を greenlet スレッドの中に ラップする gevent.spawn です。 生成された greenlet のリストが threads 配列に格納され、 gevent.joinall 関数に渡されます。この関数は今のプログラムを、与えられた すべての greenlet を実行するためにブロックします。 すべての greenlet が終了した時に次のステップが実行されます。

と書いてありますが、少し表現が難しいですね。



仮にもできるんだから、やってみたいですね。
何故やりたいか。それは、Scapyのようなstrを生成するのに時間がかかるプロセスを
効率よく分担させたいからです。

いっそうのこと、gevent勉強して、自分でTCPプロトコルスタック書いた方がいいんじゃないか
という気持ちにもかられますが、やっぱり男はファンタジスタです。

夢をもって、Scapyを追いかけたいと思います。

明日ぐらいにはScapyのクラス継承を楽しみたいなと思っております。

もし、geventに興味持ったら、公式サイト閲覧することを勧めます.