Non-blocking with Python + uWSGI

Asynchronous processing is realized by non-blocking communication with Python

There are multi-process, multi-thread, and non-blocking methods to execute another process before one process is completed. A process is a processing unit that has its own memory such as virtual memory, and memory is not shared between processes. Since a thread is a processing unit within a process, threads in the same process share memory. Non-blocking can respond to multiple requests with one thread.

Blocking communication and non-blocking communication

There are two communication methods for socket communication, "blocking communication" and "non-blocking communication". Blocking is a communication method that waits for the completion of transmission / reception and then starts other processing, so it is often used when performing sequential processing. On the other hand, non-blocking is a communication method that can start other processing even if the communication is not completed, so it is often used when performing asynchronous processing.

Non-blocking in Python 3.5

From Python 3.4, a module called ʻasyncio has been added to the standard library to enable non-blocking processing ([Reference](http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0)). About ʻasyncio, it is very easy to understand and is organized in here, and many samples are posted.

Non-blocking mode with uWSGI

Starting with ʻuWSGI 1.9, non-blocking mode is supported as described in [here](http://uwsgi-docs.readthedocs.io/en/latest/Async.html). Also, since 2.0.4, ʻasyncio is supported (Reference).

Environment

Below, execute in the environment where Python 3.5 is installed.

  1. Install greenlet
$ pip3 install greenlet
  1. Install ʻuWSGI with ʻasyncio support First, find the directory where greenlet is installed.
$ find / -name greenlet -type d

In the environment of Mac OS, the result was /Users/xxx/.pyenv/versions/3.5.0/include/python3.5m/greenlet, so execute the following.

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

Operation verification

Perform simple operation verification with blocking and non-blocking. In the sample code, the file is read, but the following is used for the file.

numbers.txt


zero
one
two
three
four
five

Also, in the verification, ʻApache Bench` was used, but when using local, [here](https://stackoverflow.com/questions/7938869/ab-is-erroring-out-with-apr-socket- It is necessary to be careful as described in recv-connection-refused-61), and this time it is executed with the following command.

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

Operation verification with blocking

$ pip3 unistall uwsgi

sample

Log the contents of the read file.

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"]

Verification

Execute with the number of processes and threads specified as 1.

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

result

ʻApache Bench` results (partial only).

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

Operation in non-broking

Log output of the contents of the file read asynchronously after 5 seconds.

sample

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"]

Verification

Execute by specifying 1 for the number of processes and threads as in the case of blocking.

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

Here, 2 is specified for --asyncio, but when it is set to 1, the following is displayed and it does not start.

the greenlet suspend engine requires async mode

Looking into ʻasync mode`, as described below, there is a memory structure called a core that stores data for each request (defined as a core because the concept of threads is confusing).

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.

These cores need to be switched, and it's probably only two or more.

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

result

Compared to blocking, this sample is slightly slower in terms of performance. It seems better to compare with other samples.

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

Since I am using 2 cores in non-blocking mode, I reinstalled ʻuWSGI` and checked the number of threads with 2. The result is below. It may be a Mac environment problem.

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

Remarks

I would like to study ʻasyncio a little more, introduce a framework such as Flask`, and continue verification that also serves as research under certain conditions such as using Nginx. Also, try other languages such as comparing with Golang.

Here says ʻIf you are in doubt, do not use async mode.`, but it is still at a practical level. Is it just not reached? Or is there a way to do it? Let's dig deeper.

Recommended Posts

Non-blocking with Python + uWSGI
WebSocket with Python + uWSGI
FizzBuzz with Python3
Scraping with Python
Scraping with Python
Python with Go
Twilio with Python
Integrate with Python
Play with 2016-Python
AES256 with python
Tested with Python
python starts with ()
with syntax (Python)
Bingo with python
Zundokokiyoshi with python
Excel with Python
Microcomputer with Python
Cast with python
Create Python + uWSGI + Nginx environment with Docker
Serial communication with Python
Zip, unzip with python
Django 1.11 started with Python3.6
Python with eclipse + PyDev.
Socket communication with Python
Data analysis with python 2
Scraping with Python (preparation)
Try scraping with Python.
Learning Python with ChemTHEATER 03
Sequential search with Python
"Object-oriented" learning with python
Handling yaml with python
Solve AtCoder 167 with python
Serial communication with python
[Python] Use JSON with Python
Learning Python with ChemTHEATER 05-1
Learn Python with ChemTHEATER
Run prepDE.py with python3
1.1 Getting Started with Python
Collecting tweets with Python
Binarization with OpenCV / Python
3. 3. AI programming with Python
Kernel Method with Python
Scraping with Python + PhantomJS
Posting tweets with python
Drive WebDriver with python
Use mecab with Python3
[Python] Redirect with CGIHTTPServer
Voice analysis with python
Think yaml with python
Operate Kinesis with Python
Getting Started with Python
Use DynamoDB with Python
Zundko getter with python
Hello World with nginx + uwsgi + python on EC2
Handle Excel with python
Ohm's Law with Python
Primality test with python
Run Blender with python
Solve Sudoku with Python
Python starting with Windows 7
Create Nginx + uWSGI + Python (Django) environment with docker