tl;dr: If handling interrupts is important, use a SyncManager (not multiprocessing.Manager) to handle shared state
I just hit the learning curve pretty hard with python’s multiprocessing — but I came through it and wanted to share my learnings.
Preliminary Thoughts
The bulk of this post is going to be around using the multiprocess library, but a few preliminary thoughts:
Multiprocessing and Threading is hard (especially in python):
Its starts off all hunky-dory — but trust me, factor in time to hit the wall … hard.
pstree is your friend.
Stop what you are doing, and pick your favorite package manager and install pstree
: (for me: brew install pstree
)
Its super-useful for dealing with sub-processes, and see what is going on.
In particular pstree -s <string>
which searches your branches containing processes that contain the string in the command line. So much better than ps
Output looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Know your options
There are more then one paralyzation framework / paradigms out there for python. Make sure you pick the right one for you before you dive-in. To name a few:
Important: Unless you are a ninja — do not mix paradigms. For example if you are using the multiprocessing library — do not use threading.locals
The Main Story
Axiom One: All child processes get SIG-INT
Note: I will use SIG_INT, Keyboard Interrupt, and Ctr-C interchangeably
Consider the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
The abbreviated output you get is as follows:
1 2 3 4 5 6 7 8 9 10 11 |
|
The main take aways are:
- Keyboard interrupt gets send to each sub process and main execution
- the order in which the run is non-determanistic
Axiom Two: Beware multiprocessing.Manager (time to share memory between processes)
If it is possible in your stack to rely on a database, such as redis for keeping track of shared state — I recommend it. But if you need a pure python solution read on:
multiprocessing.Manager bill themselves as:
Managers provide a way to create data which can be shared between different processes. A manager object controls a server process which manages shared objects. Other processes can access the shared objects by using proxies.
The key take away there is that the Manager actually kicks off a server process to manage state. Its like it is firing up your own little (not battle tested) private database. And if you Ctr-C your python process the manager will get the signal and shut it self down causing all sorts of weirdness.
Consider the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Try running that and interrupting it was a Ctr-C, you will get a weird error:
You will get a socket.error: [Errno 2] No such file or directory
when trying to access the shared_array. And thats because the Manager process has been interrupted.
There is a solution!
Axiom Two: Explicitly use multiprocessing.manangers.SyncManager to share state
and use the signals library to have the SyncManager ignore the interrupt signal (SIG_INT)
Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
Main take aways here are:
- Explicitly using and starting a SyncManager (instead of Manager)
- on its initialization having it ignore the interrupt
I will do a future post on gracefully shutting down child threads (once I figure that out ;–)
Thanks to @armsteady, who showed me the like on StackOverflow (link)