Posted at

【Python】geventライブライリについて簡単なことを整理する


はじめに

Pythonのgeventライブラリを「入門Python3」で知ったきっかけに,公式ドキュメントなどを参考にしながら,学んだことを備忘録のようにまとめる.


geventライブラリについて

geventライブラリはイベント駆動で,通常の命令型のコードを書くと,geventが手品のように部品をコルーチン(注1)に変換する.

互いに通信して相手がどこにいるのかをつねに把握できるジェネレータのよう動く.

geventはsocketなどのpythonの標準オブジェクトの多くを書き換え,ブロックせずにgeventのメカニズムを使うようにする.


(注1.)コルーチンとは処理を中断や再開する仕組みのこと

特徴として通常の関数のとは異なり、

処理を途中で抜けて任意のタイミングで中断部分から処理を再開する.



イベント駆動プログラミングについて

以下の資料がわかりやすく参考になった.


イベント駆動プログラミングとI/O多重化

https://www.slideshare.net/mizzy/io-18459625

Apache Kafka on Herokuを活用したイベント駆動アーキテクチャの設計と実装

https://www.slideshare.net/DeveloperForceJapan/apache-kafka-on-heroku



What is gevent?

geventはコルーチンベースのpythonネットワーキングライブラリ.

本稿では,geventの以下の機能について,サンプルコードを確かめながら理解を深める.


  • グリーンレットに基づく軽量の実行ユニット

  • サードパーティのモジュールに対してモンキーパッチを適応


サンプルコード


グリーンレットの機能


gevent_sample.py

import gevent

from gevent import socket

if __name__ == "__main__":
hosts = ['www.google.com','www.yahoo.co.jp','www.qiita.com']
jobs = [gevent.spawn(gevent.socket.gethostbyname,host) for host in hosts]
gevent.joinall(jobs,timeout=5)

# ホスト名のIPアドレスを出力
for job in jobs:
print(job.value)


$ python gevent_test.py

216.58.197.196
182.22.25.252
52.196.217.245


説明

socketモジュールのgethostbyname()関数は,ドメイン名に対応するIPアドレスを返す.同期的なので,世界中のネームサーバーを捕まえて.そのアドレスを解決しようと競い合うときに待ちに入る.しかし,geventバージョンを使用すれば,非同期的に実行し複数のサイトを独立で解決できる.

gevent.spawn()は個々のgevent.socket.gethostbyname(url)を実行するために,グリーンレット(グリーンスレッド,マイクロスレッド)を作る.

通常のスレッドとの違いはグリーンレットならブロックしないこと.ブロック処理とは他のスレッドがその資源にアクセスできないようする処理で,排他処理や排他制御ともいう.通常のスレッドをブロックしてしまうようなことが起きても,geventはほかのグリーンレットのどれかを制御を切り替える.

gevent.joinall()メソッドは派生させたすべてのジョブが終了するのを待つ.


geventのモンキーパッチ機能

geventバージョンのsocketではなく,モンキーパッチング関数を使うことができる.

これらはgevetnバージョンのモジュールを呼び出すのでなく,socketなどの標準モジュールがグリーンレットを使うように書き換える.

Pythonコードには機能するが,Cで書かれたライブラリには適用しない.


モンキーパッチングについて


  • 元のコードを変更することなく、動的にコードを拡張/変更する事の総称のこと.(メタプログラミングの一種)

  • ライブラリのコードを直接変えたくない時とかに利用される

  • テスト系ライブラリでよく利用される


gevent_monkey.py

import gevent

from gevent import monkey;monkey.patch_all()
import socket
import time

if __name__ == "__main__":

hosts = ['www.google.com','www.yahoo.co.jp','www.qiita.com']
#gevent.spawn()の引数にはsocket.gethostbynameを指定しているが,ここがモンキーパッチング対象
jobs = [gevent.spawn(socket.gethostbyname,host) for host in hosts]
gevent.joinall(jobs,timeout=5)
gevent.spawn()

for job in jobs:
print(job.value)



説明

genvent_test.pyでは,gevent.socketモジュールを明示的にインポートしていた.

一方,gevent_monkey.pyでは,標準ライブラリであるsocketモジュールを明示的インポートした.monkey.patch_all()を使用しない場合,import socketの段階では,gevent版のsocketモジュールではなく,標準ライブラリとしてのsocketモジュールである.

モンキーパッチング関数(サンプルコードではmonkey.patch_all())を使用することで,標準ライブラリをgevent版(gevent.socket)に書き換えることができる.


所感

ここに記述したことは全然実践的でないし,もっと調べていきたい.

知らべてわかったことを追加していく予定.


参考文献