Python+uWSGIでノンブロッキング


#Pythonでノンブロッキング通信で非同期処理を実現する
ある処理が完了する前に別の処理を実行する方法として、マルチプロセス、マルチスレッド、ノンブロッキングがある。プロセスは仮想メモリといった固有のメモリを持つ処理単位になり、プロセス間同士でメモリが共有されない。スレッドはプロセス内での処理単位となるため、同じプロセス内のスレッドはメモリが共有される。
ノンブロッキングは1スレッドで複数のリクエストに対して応答することができる。

##ブロッキング通信とノンブロッキング通信
ソケット通信では「ブロッキング通信」と「ノンブロッキング通信」の2つの通信方法がある。ブロッキングは送受信の完了を待ってから他の処理を開始する通信方法のため、逐次的な処理をする時に利用することが多い。一方、ノンブロッキングは通信が完了していなくても他の処理の開始ができる通信方法のため、非同期処理をする時に利用することが多い。

##Python3.5でのノンブロッキング
Python3.4から標準ライブラリに`asyncio`というモジュールが追加され、ノンブロッキング処理ができるようになった（[参考](http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0)）。
`asyncio`については[こちら](http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0)に非常にわかりやすくまとまっていて、サンプルも多数掲載されている。

##uWSGIでノンブロッキングモード
`uWSGI1.9`から、[こちら](http://uwsgi-docs.readthedocs.io/en/latest/Async.html)に記載されているようにノンブロッキングモードがサポートされるようになった。
また、2.0.4から`asyncio`がサポートされた（[参考](http://uwsgi-docs.readthedocs.io/en/latest/asyncio.html)）。

##環境構築
以下、Python3.5がインストールされている環境で実行する。

1. `greenlet`のインストール

```shell-session
$ pip3 install greenlet
```

2. `asyncio`がサポートされた`uWSGI`をインストールする
まずは、`greenlet`がインストールされているディレクトリを探す。

```shell-session
$ find / -name greenlet -type d
```

`Mac OS`の環境では、結果が`/Users/xxx/.pyenv/versions/3.5.0/include/python3.5m/greenlet`であったので、下記を実行。

```shell-session
$ CFLAGS="-I/Users/xxx/.pyenv/versions/3.5.0/include/python3.5m" UWSGI_PROFILE="asyncio" pip3 install uwsgi
```

##動作検証
ブロッキングとノンブロッキングで簡単な動作検証をする。
サンプルコードではファイル読み込みをしているが、そのファイルのは下記を利用した。

```text:numbers.txt
zero
one
two
three
four
five
```

また、検証では、`Apache Bench`を利用したが、ローカルを利用する場合、[こちら](https://stackoverflow.com/questions/7938869/ab-is-erroring-out-with-apr-socket-recv-connection-refused-61)に記載があるように注意が必要で、今回は下記のコマンドで実施。

```shell-session
$ ab -n 10000 -c 100 http://127.0.0.1:9090/
```

###ブロッキングでの動作検証
※この場合、`uWSGI`を`pip3 install uwsgi`でインストールする必要がある。既にノンブロッキングモードでインストールしている場合、下記でいったん`uWSGI`をアンインストールする。

```shell-session
$ pip3 unistall uwsgi
```

####サンプル
読み込んだファイルの内容をログ出力する。

```py3
def application(environ, start_response):
    def my_generator(name):
        with open(name) as lines:
            yield from lines

    g = my_generator("numbers.txt")
    for k, v in enumerate(g):
        print("%s:%s" % (k, v), end="")

    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]
```
####検証
プロセスとスレッド数を1に指定して実行。

```shell-session
$ uwsgi --http-socket :9090 --processes 1 --threads 1 --logto uwsgi.log --wsgi-file webapp.py
```

####結果
`Apache Bench`の結果（一部のみ）。

```shell-session
Requests per second:    2694.06 [#/sec] (mean)
Time per request:       37.119 [ms] (mean)
Time per request:       0.371 [ms] (mean, across all concurrent requests)
Transfer rate:          144.70 [Kbytes/sec] received
```

###ノンブロキングでの動作
非同期で5秒後に読み込んだファイルの内容をログ出力。

####サンプル
```py3
import asyncio


@asyncio.coroutine
def my_generator(name):
    with open(name) as lines:
        yield from lines

def read():
    g = my_generator("numbers.txt")
    for k, v in enumerate(g):
        print("%s:%s" % (k, v), end="")


def application(environ, start_response):
    asyncio.get_event_loop().call_later(5, read)
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]
```

####検証
ブロッキングのときと同様にプロセスとスレッド数を1に指定して実行。

```shell-session
$ uwsgi --asyncio 2 --http-socket :9090 --greenlet --processes 1 --threads 1 --logto uwsgi.log --wsgi-file webapp.py
```

ここで、`--asyncio`に`2`を指定しているが、`1`にすると下記が表示され、起動しなかった。

```shell-session
the greenlet suspend engine requires async mode
```

`async mode`について調べてみると下記の記述にあるように、コアという、リクエスト単位のデータを格納するメモリ構造（スレッドの概念の紛らわしいのでコアと定義されている）があるとのこと。

```text
Technically, cores are simple memory structures holding request’s data, but to give the user 
the illusion of a multithreaded system we use that term.
```

これらのコアは切り替える必要があって、2個以上ないとだめなのだろう。

```text
Each core can manage a single request, so the more core you spawn, more requests you will be
able to manage (and more memory you will use). The job of the suspend/resume engines is to stop
the current request management, move to another core, and eventually come back to the old one
(and so on).
```

####結果
ブロッキングのときと比較して、今回のサンプルではパフォーマンス面で若干遅い。
他のサンプル等で比較したほうがいいと思われる。

```shell-session
equests per second:    2336.16 [#/sec] (mean)
Time per request:       42.805 [ms] (mean)
Time per request:       0.428 [ms] (mean, across all concurrent requests)
Transfer rate:          125.48 [Kbytes/sec] received
```

ノンブロッキングモードで2コアを利用しているので、`uWSGI`を再インストールし、スレッド数を2で確認してみた。その結果は以下。Mac環境の問題かもしれない。

```shell-session
Requests per second:    2691.94 [#/sec] (mean)
Time per request:       37.148 [ms] (mean)
Time per request:       0.371 [ms] (mean, across all concurrent requests)
Transfer rate:          144.59 [Kbytes/sec] received
```

##備考
`asyncio`についてもう少し研究し、また`Flask`といったフレームワークを導入し、Nginxも使うなどある程度の条件下で、研究を兼ねた検証を続けたい。また、Golangとも比較するなど他の言語でも試していく。

[こちら](http://uwsgi-docs.readthedocs.io/en/latest/Async.html)に`If you are in doubt, do not use async mode.`と記載があるが、まだまだ実用レベルには至っていないだけなのだろうか。それとも然るべきやる方があるのだろうか、深掘りしていこう。