Chapter 2

Why is asynchronous programming more efficient at handling I/O concurrency than multiple processes/threads?

Asynchronous programming is a very effective solution when dealing with I/O concurrency because it allows us to multiplex I/O actions without memory or computing overhead. Alternative solutions that are based on multithreading or multiprocesses require either more CPU either more memory, or even both.

Multithreading hits a limit when 1,000 threads are running concurrently. On heavy workloads, this puts some pressure on the OS scheduler and ends up wasting a lot of CPU resources due to contention.

Multiprocess solutions face the same problem, but also require more memory because the address space of the program is allocated for each instance of the program. Some of this memory is shared between these instances (such as the code sections), but a big part has to be replicated.

Why is asynchronous programming not a solution to improve performances on CPU-bound tasks?

Asynchronous programming cannot improve performance on CPU-bound tasks because it executes on a single core of the CPU. To improve the performance of a CPU-bound task, you have to parallelize the execution of this task on multiple cores. This is the situation that multithreading or multiprocess solutions are ideal for.

Why does multithreading not perform as well on Python as on other programming languages?

The Python interpreter (CPython) is implemented with a lock that prevents the execution of Python code from several threads in the same process. This lock is called the Giant Interpreter Lock. The consequence of this is the fact that several threads in a process cannot execute concurrently, even on a processor that has several cores. There are two ways to deal with this constraint:

  • Use multiprocess instead of multithreading. Python code in two processes can execute simultaneously because they run in two different instances of the interpreter.
  • Execute native code from Python. Many libraries are native libraries (that is, libraries implemented in C or C++ for example) with Python bindings. Native code from these libraries can run concurrently because the GIL is released when the interpreter runs native code.

What is the benefit of a generator compared to an iterator?

A generator is a simpler way to write an iterator. The main benefit of using a generator is the possibility of generating a very long, or even an infinite, sequence without loading it all in memory.

Why does a generator help with writing asynchronous code?

A generator helps with writing asynchronous code that looks like synchronous code, that is, code where logical operations follow each other instead of being spread throughout multiple locations in the program. This is possible thanks to a very special property of generators: they can be interrupted at a location and be resumed later to that location, with the execution context being saved and restored. In practice, this allows us to interrupt the execution of a function when waiting for an asynchronous operation to complete, and resume the execution of this function when the operation has completed.

What is the difference between a calling the next function and calling the send method of a generator object?

The next function resumes the operator from its last yield point. The send method allows us to additionally send back a value to the generator. The value passed as an argument of the send method can be retrieved as the return value of the yield expression. The combination of the yield expression and the send method allows a generator and its clients to communicate together in a bidirectional way. The generator can send values to its client when yielding, and the client can send values to the generator by using the send method.

How is a coroutine declared?

The correct way to declare a coroutine is by using the async keyword. However, this has only been available since Python 3.5:

async def foo():
await something()
print("foo")

It is also possible to declare a coroutine as a generator, and decorate it with @asyncio.coroutine:

@asyncio.couroutine
def foo():
yield something()
print("foo")

This last notation works for all versions of Python (with support for AsyncIO), but should be avoided whenever possible.

How can a coroutine call another coroutine?

A coroutine calls another coroutine by using the await expression. The await expression suspends the execution of the coroutine until the awaited coroutine completes. The result of the awaited coroutine is returned as the result of the await expression when the initial coroutine is resumed.

What is the role of the event loop?

The event loop schedules the execution of tasks and suspends the main thread activity when no task is active.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.163.13