Libraries
Standard multiprocessing
Pebble - pretty close to the standard one, but with a bit nicer interface
Dask - well maintained and (almost) drop-in replacement of numpy and pandas:
|
|
mptools - seems like an abandoned project. The autor had a nice article though: Things I Wish They Told Me About Multiprocessing in Python
Related article: 10x Faster Parallel Python Without Python Multiprocessing
Progress bar for parallel tasks
Often one need to run some slow function in parallel in order to speed up the computation.
In user facing apps it’s important to visualize the progress.
One can use multiprocessing.Pool
and tqdm
for it.
|
|
One cannot apply tqdm to the Pool.map
because the whole processing happens before the tqdm can iterate the result.
However it’s possible to use Pool.imap
or Pool.imap_unordered
if the order is not important.
|
|
It’s nice that we don’t need to do pool.join()
here because imap*
waits for all the tasks to complete.
Don’t forget to set chunk size:
For very long iterables using a large value for chunksize can make the job complete much faster than using the default value of 1.
Alternatively we can use callbacks to update a global progress bar in a main process:
|
|
That method requires pool.join
to wait for all the processes to finish.
Using Pipes for parallel stateful processes
Let’s consider the following task. We have to implement a controller. The controller defines a processing graph with 4 interconnected stages:
detector
size_estimator (depends on detector)
classifier (depends on detector)
aggregator (depends on size_estimator and classifier)
This could be a model for some computer vision pipeline and controller processes frames coming from a camera.
Sequential version of the controller could look like this
|
|
Usage of the controller:
|
|
As I have mentioned above two stages - size estimation and classification - can be executed in parallel.
A standard solution for that could be multiprocessing.Pool
with a function like map
or imap
.
That works if our processing stages are stateless and the initialization is cheap
It is not always the case.
If classifier
requires a costly initialization, e.g. loading a big neural network into memory, it would be
nice to have it initialized only once. We can do it in a separate process.
If we were using a programming language other than Python, we could use threads for it.
But in python we have GIL.
Controller has to send data to another process and receive the results.
Communication between parallel processes is a dangerous thing.
Let’s try to use multiprocessing.Pipe
for that.
|
|
pipe_worker
would look like this:
|
|
Comparing those two controllers:
|
|
Sample output:
sequential
FPS: 1.7753397948365568
parallel
worker started
worker exited correctly
FPS: 2.820896038347416
Full code is here.
There are some alternative workarounds to deal with initialization in standard multiprocessing
.
They usually require some global variables. See for example:
Stack overflow how to use initializer to set up my multiprocess pool?
Multiprocessing.Pool - Pass Data to Workers w/o Globals: A Proposal
Processing KeyboardInterrupt in workers
Apparently there are some issue with KeyboardInterrupt and multiprocessing:
when workers are idle, Python’s KeyboardInterrupt is not handled correctly by the multiprocessing module, which results in not only a lot of stacktraces spewed to the console, but also means the parent process will hang indefinitely.
Python: Using KeyboardInterrupt with a Multiprocessing Pool, 2011.