From PEP 492 to the era of asynchrony

June 2, 2023

Asynchrony can be implemented in many programming languages, including Python. However, asynchrony in early Python was not as widely used as Javascript or C# because of its difficult implementation. In this article, I will introduce the history of asynchronous programming in Python and its growth at the present time.

1. Asynchronous

When people solved the "c10k problem" and then the advent of Nginx, asynchronous programming was more interesting and improved to make it easier to install than its original version.

In the original version, you must know concepts such as non-blocking socket, multiplexer, selector, ... of the OS to be able to implement an asynchronous processing application:

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    exit(1);
}
	
last_fd = sockfd;

my_addr.sin_family = AF_INET;         /* host byte order */
my_addr.sin_port = htons(MYPORT);     /* short, network byte order */
my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */

if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
    perror("bind");
    exit(1);
}

if (listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
}

if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
	perror("accept");
}

fcntl(last_fd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state	*/
fcntl(new_fd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state	*/

	while(1){
		for (i=sockfd;i<=last_fd;i++){
			printf("Round number %d\n",i);
       			if (i = sockfd){
                ...

Now, using asynchronous in Python is a peace of cake because of the syntactic improvement. The async/await syntax was first introduced by C# and then implemented by many other programming languages like Javascript, Python, Java, C++, Rust, Swift,...

Asynchronous "programmable technique" switches to "the syntence of a programming language".

2. Asynchrony before PEP 492

Asynchronous in Python is completely possible because Python supports non-blocking socket APIs of OS such as selector, and multiplexing,... However, using these APIs to deploy an asynchronous application is quite difficult.

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("0.0.0.0", port_number))
server_socket.listen(max_clients)

inputs = [server_socket]
outputs = []

while True:
        readable, writable, exceptional = select.select(inputs, outputs, inputs, 1)
        for s in readable:
                if s is server_socket:
                connection, client_address = s.accept()
                connection.setblocking(0)
                inputs.append(connection)
                threading.Thread(target=handle_client, args=(connection, client_address)).start()

As a result, programmers have combined those APIs with coroutines to perform asynchronous implementations.

Coroutine is a common concept in many programming languages and was first introduced in Python with PEP 342 by Guido van Rossum and Phillip J. Eby proposed in 2005, in Python version 2.5.

If you want to better understand how to use a coroutine to implement asynchronous programming, you can read your 2 articles below for more detail:

One of the famous frameworks that implement asynchronous is Tornado.

Tornado is a networking framework that implements asynchronous on coroutine for the following purposes:

  • TCP/UDP server
  • HTTP server và HTTP client
  • Mail server

3. The birth of PEP 492 and the async/await era

In 2015, PEP 492 was born and proposed by Yury Selivanov. By using coroutine to implement the syntax of async/await, Python has been able to make asynchronous operators much easier for programmers.

Instead of having to work with low-level APIs, working with a pure coroutine, now, we only need to know two extremely popular keywords: async/await

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.setblocking(False)
listener.bind((host, port))
listener.listen(1024)

loop = asyncio.get_event_loop()
while True:
    client, addr = await loop.sock_accept(listener)
    loop.create_task(handle_client(client))

The author also said reasons for the async/await syntactic in his proposal:

The growth of the Internet and general connectivity has triggered the proportionate need for responsive and scalable code. This proposal aims to answer that need by making writing explicitly asynchronous, concurrent Python code easier and more Pythonic.

As mentioned above, async/await itself is just a syntax designed to make the code more readable. What is actually processed behind it is the coroutine, so it is not wrong to say that Python's asynchronous functionality is implemented by coroutines.

4. ASGI and the rise of asynchronous web servers

Before ASGI was born, WSGI was the popular standard for web applications to communicate with web servers. This standard was introduced by PEP 333 in late 2003. It focuses on developing an interface based on the synchronous API of the network: blocking. Therefore, to implement scale performance, multi-thread is used in combination, similar to the case of Apache HTTP Server.

We have web servers implementing this standard such as Gunicorn, uWSGI or Nginx with the support of ngx_http_uwsgi_module module.

Some web frameworks implement this standard such as Django, Flask, Bottle,...

In September - 2018, version 1.0 of ASGI was released, opening a new era of web development. With the birth of ASGI, a series of asynchronous support libraries and frameworks were born such as Uvicorn, Hypercorn, Django v3, Fastapi, Starlette, Quart, Sanic,...

People gradually know and use asynchronous frameworks more thanks to the support of these web frameworks.

With the database, we have:

With the HTTP client, we can use httpx, it has a completely similar interface to requests.

For a web framework, we have:

For web servers, there are:

There are many other libraries that support asynchrony that we can use.

Currently, on Github, some groups worth paying attention to this trending can be mentioned such as:

Some notable individuals such as:

5. Recap

Python is really changing a lot in web development in general and the stack handles asynchronous - scalable applications in general. Let's take a look at a few milestones before ending the article

  • 1991: Python was created and supports low-level APIs for non-blocking
  • 2003: WSGI was born - opening the era of web development in Python
  • 2005: PEP 342 introduces coroutine
  • 2015: PEP 492 introduces syntax async/await
  • 2018: ASGI was born, marking the explosive era of asynchronous frameworks

I hope that through this article, you can better understand the development history of the Python real estate ecosystem in general and the development and trends in web development in particular of Python.