2016年1月4日0件のコメント最近bottleとgeventを使ってみて、とても便利でメカニズムも興味深いと思ったのだが、あまり日本語の解説がないようなのでここにまとめてみる。Pythonによるウェブアプリ開発で、レスポンス速度が重要なときに参考になるかと思う。bottleとは?Pythonの軽量ウェブフレームワークである。使い方はとてもシンプルで、独自のテンプレートエンジンを持っている。詳細は本家のドキュメントを参照だが、その本家のドキュメントの最初のこの例を示せば大体の雰囲気はつかめるであろう。hamukazu!」と表示される。geventとは?非同期処理をベースとしたネットワーク処理のライブラリである。gevent」でインストールできる。geventでは軽量な擬似スレッドによってIOの待ち時間に他の処理を行うことができる非同期なメカニズムが用意されている。ここでは、bottleとの連携についてのみ説明するので、geventについて深い話はしない。詳細は本家ドキュメントを参照されたい。3333)により定められている。サーバへのリクエストがくると、アプリケーションは「レスポンスのチャンクを返すイテレータ」を返す。サーバはそのイテレータを繰り返し呼び出すことでチャンクを少しずつ受け取り、ソケットに書き込むことでレスポンスをクライアントに返す。アプリケーション側は、レスポンスをいくつかに刻んで、呼ばれるたびに少しずつ返せばよい。上記のサンプルコードではわかりづらいと思うので、明示的にイテレータを使った例を次に示す。port=8080)これを起動して、ブラウザで「http://localhost:8080/」にアクセスすると、「Spam!Ham!」(途中改行あり)と表示される。このプログラムでは、レスポンスとして返すHTML文書をそのまま返すのではなく、2つの部分に分割してyieldしている。Pythonのイテレータのお作法により最後にStopIterationを返す。サーバは、StopIterationが出てくるまで繰り返し呼び出すことでクライアントに返すべきHTML文書を取得している。bottleとgeventを使った開発bottleにかぎらず、多くのウェブサーバ(ウェブアプリ)は複数のスレッドでサーバへのリクエストを処理する。一方でスレッド間のコンテキストスイッチ切り替えのコストを考え、同時に動くスレッド数は20個程度に抑えられているのが通常である。一方で、ウェブアプリではその内部処理でデータベースアクセスや他サーバへのアクセスを伴うことが多いので入出力の処理待ちがネックになり、スレッド数とその待ち時間の長さによりシステムの処理能力が決まってしまう。その待ち時間を有効活用するために、新たなスレッドを立ち上げることなく擬似的なスレッドで他のリクエストを処理しようというのがbottleとgeventを使うときの考え方である。擬似スレッドへのコンテキストスイッチのコストは通常のスレッドと比べるとかなり低くなっている。入出力待ち状態のスレッドが、ソケットに溜まっているクライアントからのリクエストを見に行きもしあればそれを処理するというメカニズムになっている。モンキーパッチgeventではモンキーパッチという仕組みにより、既存のネットワーク関連ライブラリの中身をgevent版に置き換えることで、手軽な非同期化を実現している。monkeymokey.patch_all()とすれば、その後にインポートされるライブラリのしかるべき部分がgevent版に置き換わる。先程のイテレータを使った例を少し書き換えて次のようにしてみる。port=8080)ここでの変更点は、1)モンキーパッチのためのオマジナイを最初に入れた、2)イテレータのyieldの間にsleepを入れた、3)runの引数に「server=”gevent”」を加えた、という点だけである。ここで、モンキーパッチによりtime.sleepはgevent版のsleepに置き変えられている。このsleep中に同じスレッドないで他のリクエストを処理することも可能になっている。サーバ側にもgeventを使っていることを知らさなければいけないので、runの引数に「server=”gevent”」を忘れてはいけない。他のライブラリの関数の中身を勝手に書き換えてしまうのはとても気持ち悪いし悪魔的な印象を受けるのだが、これはよく黒魔術と呼ばれるものである。bottleとgeventを使ったデザインパターンここではよくあるデザインパターンとして一つだけ紹介する。次のようなコードがそのパターンである。port=8080)いうまでもなくdo_something1()とdo_something2()は実際に何らかの処理が入る。index()の中で関数worker()が定義されていて、bodyという変数に格納されたキューが共有されている。このキューにより非同期な処理が実現されている。gevent.spawnは関数workerを非同期で呼び出しており、呼出し後すぐに次の行に制御が移されて、変数bodyが返される。サーバはこのbodyをイテレータとみなして逐次呼び出すが、worker内でputされるまでは次の値が取り出せないようになっている。この仕組みにより、もしdo_something1()などが入出力に時間がかかる処理で待ち時間が発生したとしても、その間にgeventが他の擬似スレッドに他のリクエストに対する処理を割り当てることができる。ベンチマーク簡単のため与えられた値をキーにデータベースにアクセスしてその値を返すだけのウェブアプリを考え、geventと使わない場合と使った場合について処理能力を比較してみる。データベースにはキーバリュー型のlmdbを使うが、lmdbを使う理由は設定が簡単だからである。lmdb」でインストールしておく必要はある)まずは準備としてカレントディレクトリにdbというディレクトリを作って、データを保存するコードを1回だけ実行する。以下pythonのバージョンは3系を仮定するが、データベース保存・読み込みのエンコード部分だけ気をつければ2系でも動くはずである。v.encode("utf-8"))これはただ、”0′′というキーに”000000′′、”1′′というキーに”000001′′、というふうに999まで保存するだけである。これを読み込んで保存するウェブアプリを作る。geventを使わない版の方から。port=8080)これを実行して、ブラウザで「http://localhost:8080/99」を開くと「00099」が表示されるはずである。これはただデータベースから取り出した値を表示しているだけである。port=8080)ここでウェブアプリのテストツールであるlocustを使ってベンチマークをとってみた。locustの使い方やここで用いたテストコードは説明が長くなるので省略するが、1秒間に1000アクセスする負荷をかけてみると、geventなし版の方は処理エラーが多発し全く処理ができなくなったが、gevent版の方は問題なく処理できた。環境に依存するだろうが、手元の環境ではそうなった。コメントをキャンセルメールアドレスが公開されることはありません。