Your backend code will need to run asynchronously at times.
Suppose we have two distinct tasks which both involve repeating a smaller task n number of times.
Given the letters of the English alphabet:
We have our first task:
What it does, in order, is; for each letter in the
- Fetch some data from an endpoint.
- Write the response to a local file with the filename format
The second task:
Given a list with the response loaded from the files, perform some (silly) numerical operations on the contents and print the result to
It should be noted here that though the
load_letters() function will be called as part of the task, it is merely a prerequisite of the task. The actual task is processing the data.
IO bound v/s CPU bound
In the above examples, it can be noted that we have two distinct types of tasks at hand.
The first one is IO bound, meaning the CPU is lying "idle" while Python "waits" on operations to finish. IO bound operations are tasks include but are not limited to writing to disks, network calls and database operations.
Let's try both multithreading and multiprocessing to each of these tasks and benchmark how they perform.
Task 1 using Multithreading
Let's modify our
download_and_write() function to take in the letter as an argument. Let's also return the filename just so we have some kind of return from the function.
Adding the requisite imports, some driver code to execute and benchmark and the
Running this gives us:
a.txt b.txt ... y.txt z.txt 0.84325
The last line is our benchmark score, using
Now let's try the same task but with multiprocessing instead of multithreading.
Task 1 using Multiprocessing
Fortunately, using the
concurrent.futures as we have, only minimal changes need to be made to the code. Replacing
ProcessPoolExecutor is the only change we need to make.
Now, running the script gives us.
a.txt b.txt ... y.txt z.txt 3.18596
That's a difference by a factor of almost 4x!
Onto task 2.
Task 2 using Multithreading
process_letters() function to take in the letter as an argument and return the result instead of printing it
Modifying the driver code:
And running outputs:
1582.0594177064766 1582.0006313131312 ... 1582.4486241727623 3065.616161616161 114.31489
Again, the last line in our output is the benchmark score. Let's try the same for multiprocessing now.
Task 2 using Multiprocessing
The output is a lot more promising.
1582.200505050505 1581.8578643578644 ... 1582.4486241727623 3065.616161616161 33.35094
Factor of almost 3.5x.
Hence, we have witnessed that for CPU bound tasks, use multiprocessing and for IO bound operations, use multithreading.
Code for this example can be found here.