8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-07-01

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

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

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

##uWSGIでノンブロッキングモード
uWSGI1.9から、こちらに記載されているようにノンブロッキングモードがサポートされるようになった。
また、2.0.4からasyncioがサポートされた(参考)。

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

  1. greenletのインストール
$ pip3 install greenlet
  1. asyncioがサポートされたuWSGIをインストールする
    まずは、greenletがインストールされているディレクトリを探す。
$ find / -name greenlet -type d

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

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

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

numbers.txt
zero
one
two
three
four
five

また、検証では、Apache Benchを利用したが、ローカルを利用する場合、こちらに記載があるように注意が必要で、今回は下記のコマンドで実施。

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

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

$ pip3 unistall uwsgi

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

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に指定して実行。

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

####結果
Apache Benchの結果(一部のみ)。

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秒後に読み込んだファイルの内容をログ出力。

####サンプル

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に指定して実行。

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

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

the greenlet suspend engine requires async mode

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

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個以上ないとだめなのだろう。

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).

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

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環境の問題かもしれない。

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とも比較するなど他の言語でも試していく。

こちらIf you are in doubt, do not use async mode.と記載があるが、まだまだ実用レベルには至っていないだけなのだろうか。それとも然るべきやる方があるのだろうか、深掘りしていこう。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?