A system's concurrency is the degree to which the system is able to perform work simultaneously instead of sequentially. An application written to be concurrent in general, can execute more units of work in a given time than one which is written to be sequential or serial.
When we make a serial application concurrent, we make the application better utilize the existing compute resources in the system—CPU and/or RAM—at a given time. Concurrency, in other words, is the cheapest way of making an application scale inside a machine in terms of the cost of compute resources.
Concurrency can be achieved using different techniques. The common ones include the following:
There are other forms of concurrent computing, but in this chapter, we will focus our attention on only these three.
Python, especially Python 3, has built-in support for all these types of concurrent computing techniques in its standard library. For example, it supports multi-threading via its threading
module, and multiple processes via its multiprocessing
module. Asynchronous execution support is available via the asyncio module. A form of concurrent processing that combines asynchronous execution with threads and processes is available via the concurrent.futures
module.
In the coming sections we will take a look at each of these in turn with sufficient examples.
We will take a brief look at the concept of concurrency and its close cousin, namely parallelism.
Both concurrency and parallelism are about executing work simultaneously rather than sequentially. However, in concurrency, the two tasks need not be executed at the exact same time; instead, they just need to be scheduled to be executed simultaneously. Parallelism, on the other hand, requires that both the tasks execute together at a given moment in time.
To take a real-life example, let's say you are painting two exterior walls of your house. You have employed just one painter, and you find that he is taking a lot more time than you thought. You can solve the problem in these two ways:
Two threads performing bytecode computations in a single core CPU do not exactly perform parallel computation, as the CPU can accommodate only one thread at a time. However, they are concurrent from a programmer's perspective, since the CPU scheduler performs fast switching in and out of the threads so that they appear to run in parallel.
However, on a multi-core CPU, two threads can perform parallel computations at any given time in its different cores. This is true parallelism.
Parallel computation requires that the computation resources increase at least linearly with respect to its scale. Concurrent computation can be achieved by using the techniques of multitasking, where work is scheduled and executed in batches, making better use of existing resources.
We will start our discussion of concurrent techniques in Python with multithreading.
Python supports multiple threads in programming via its threading module. The threading module exposes a Thread
class, which encapsulates a thread of execution. Along with this, it also exposes the following synchronization primitives:
Lock
object, which is useful for synchronized protected access to share resources, and its cousin RLock
Condition
object, which is useful for threads to synchronize while waiting for arbitrary conditionsEvent
object, which provides a basic signaling mechanism between threadsSemaphore
object, which allows synchronized access to limited resourcesBarrier
object, which allows a fixed set of threads to wait for each other, synchronize to a particular state, and proceedThread objects in Python can be combined with the synchronized Queue
class in the queue module for implementing thread-safe producer/consumer workflows.
3.144.252.204