Asynchronous coding is not an entirely new concept - it has been a big topic over the last few years, but is just now being used on a wider scale. As opposed to traditional, linear programming - where things follow a precise order and every step must be completed before the next one can even begin - in asynchronous processes, none of the steps have to be finished before moving forward. So, how does it work, exactly?
For example, if your script requests data from a number of different servers, executing the instructions step-by-step can be very time-consuming. Why? Because the script can’t do anything while it’s waiting for a server to respond, and it certainly can’t move on to the next one. That’s a synchronous program operating in a single thread.
In a multi-threaded environment, where you have a number of threads and usually even more tasks (like requests) to deal with, each thread takes up one task to accomplish. Then whichever thread becomes free and available can move down the line to the next task.
An asynchronous program doesn’t have to wait for the request to be executed if that would take too much time. In a single thread, it would just skip the first request, move on to the next one, and then come back later to the one it skipped to pick up right where it left off.
Having multiple threads, an async program is not only able to handle a number of tasks at a time (which is called concurrency), but also process the same task by different threads. For example, a task can be started by one thread and then completed by another one - one that happens to be free at the moment. This maximizes thread utilization.
And one more thing. Speaking of multi-threaded environments, it would be an oversight not to mention GIL - Global Interpreter Locker. When it comes to Python, this is connected with two implementations of the programming language - CPython and PyPy. GIL is a mutex that prevents multiple threads from being in a state of execution at once. No wonder it is so controversial, as this significantly impacts the performance of Python programs by becoming a bottleneck. So, why is it used? Because CPython extensions require thread-safe memory management that GIL provides. It can degrade the performance of a program, but on the other hand it also makes it possible to avoid complications between threads.
OK. So, now that we have the basics covered, let’s see how asynchronous coding is done - specifically in Python - by examining a few of the most significant concepts.
asyncio is a Python core library designed specifically to make asynchronous code simpler and more readable (almost like synchronous code), without any callbacks. It uses an async/await syntax, including constructs like:
event loops - this manages the execution of various tasks. It’s the central executor responsible for handling input/outputs (I/O) and system events;
coroutines - these are special functions, similar to the Python generator (only they don’t generate data - they consume it) and they are used for cooperative multitasking. They can give up control to their callers in order to enable running multiple applications at the same time;
tasks - these are used to schedule coroutines concurrently.
With asyncio, you can write single-threaded concurrent code. How does it work? In a nutshell, the subtasks are defined as coroutines, so you can schedule thanem as you want. For example, you could schedule them simultaneously with the event loop. In the coroutine yield points, you can also define some points where a context switch can occur. This will happen only if there are pending tasks. If there aren’t any - then no context switch.
This enhanced version of CPython supports the concept of microthreads (called tasklets), allowing developers to program in a thread-based environment, reducing complexity and performance problems to a minimum.
In Stackless, microthreads handle the accomplishment of various subtasks in the same CPU core, as an alternative to conventional event-based async coding. With additional features like communication channels, scheduling, task serialisation, and support for coroutines, the programmer will notice a significant increase in efficiency, with cleaner code and overall improved structure of the entire application.
This, in turn, is a primitive spin-off of micro-threaded Stackless. Greenlet is provided to Python as a C extension module. It doesn’t feature implicit scheduling and can be handy and effective when you want to maintain control over when your code runs and also when your work has relatively small I/O.
On top of greenlet, you can build microthreads and schedule them as you wish. But it can also be used on its own in order to make advanced control flow structures. However, greenlet uses Web Workers and supports browser environments only.
Gevent is a library based on coroutines that use Greenlet to provide a high-level synchronous API, one that re-uses concepts like events and queues taken from the Python standard library. It features a fast event loop built on top of libev or libuv, lightweight and greenlet-based execution units, thread pools, and cooperating sockets with SSL support.
Gevent is pretty powerful when it comes to minimizing the overhead connected with conventional threading. It can be used for concurrency implementation with databases, web servers, and messaging frameworks. However, it’s not really suitable for multicore CPU-bound programs.
One of these reasons is app performance. You certainly don’t want to waste most of the time waiting for tasks to be finished. Another reason, tightly connected with the first one, is user experience. The faster and more smoothly the application works, the higher the chances that users will stay and continue to use it with pleasure.
So, dive deep into the topic and check to see whether async programming is something that you can take advantage of. In some cases, it is not necessary at all, but be aware that there are programs which rely only on this type of coding.