When should a library use multiprocessing.get_context() instead of set_start_method()?
answer
- One call mutates the whole process
- The other hands back an object
- Applications may, libraries may not
- force=True overrides someone else's decision
- Executors accept the same idea as a parameter
basics
~10 smultiprocessing.set_start_method() mutates process-global state and is meant to be called once by the application. A library should call multiprocessing.get_context("spawn") and use that context's Process, Pool and Queue, leaving everyone else's choice untouched.
solid answer
~40 s`set_start_method()` sets the default for the **whole process**; it is intended to be called once, early, in the main module under the `__main__` guard, and calling it a second time raises `RuntimeError` unless you pass `force=True` — which silently overrides whatever another component chose. `get_context("spawn")` instead returns a context object exposing the same API — `Process`, `Pool`, `Queue`, `Manager` and so on — bound to that one start method, with no global effect. So the rule is: the **application** may call `set_start_method()`; a **library** uses a context and keeps its choice local. `concurrent.futures.ProcessPoolExecutor` takes the same idea through its `mp_context` argument. Note that objects from different contexts should not be mixed, and `get_context()` with no argument returns a context for the current default.
code
python · 13 linesimport multiprocessing as mp
def work(n):
return n * 2
def run(values, ctx=None):
ctx = ctx or mp.get_context("spawn")
with ctx.Pool(2) as pool:
return pool.map(work, values)
if __name__ == "__main__":
print(mp.get_all_start_methods())
print(run([1, 2, 3]))go deeper
Know that the start method can be chosen, and that get_context("spawn") gives you an object with the usual Process and Pool on it. You are unlikely to be asked for more than that.
Explain that one API mutates a process-global default and can only be set once, while the other returns a local context, and say which belongs in an application versus a library.
Show the blast radius: flipping the global changes guard requirements, picklability, inherited state and start-up cost for every component in the process, including ones you did not write.
Own it as an API-design rule — components configure themselves through injected contexts rather than global mutation, and the start method becomes one explicit, reviewed decision made at the application's edge.
This is a small API question that turns out to be a design question about global state. ### The two APIs `multiprocessing.set_start_method(method)` changes a process-global default. Everything created afterwards through the top-level `multiprocessing` functions — `Process`, `Pool`, `Queue`, `Manager` — uses it. It is documented to be called at most once, in the main module, inside the `if __name__ == "__main__":` guard, before any process is created. A second call raises `RuntimeError` unless you pass `force=True`, and `force=True` is precisely the dangerous option: it silently overrides a choice some other component of the program made for its own correctness. `multiprocessing.get_context(method)` returns a **context object** — the same public API, bound to one start method, with no global side effect. `ctx = multiprocessing.get_context("spawn")` then `ctx.Process(...)`, `ctx.Pool(...)`, `ctx.Queue()`. Two components in one program can hold different contexts and neither disturbs the other. Called with no argument it returns a context corresponding to the current default, which is useful when you want a stable handle rather than repeated global lookups. ### Why the distinction matters Start method is a whole-program property in a way that most configuration is not. It determines whether the `__main__` guard is required, whether module-level state is inherited, whether the arguments you pass must be picklable, and whether forking your threaded process is going to deadlock. A library that flips it globally can break an unrelated part of the same application: code that relied on inherited state suddenly gets an empty cache; code that passed an unpicklable argument suddenly raises; a program that ran fine on Linux acquires a new per-worker start-up cost. The inverse is also true — a library that quietly relies on the *ambient* default is fragile in a different way, because the default is not the same on every platform and changed on Unix in Python 3.14. If your library genuinely cannot survive `fork`, saying so with `get_context("spawn")` is both safer and more honest than depending on the caller having configured things your way. ### The rule The application owns the global; libraries own contexts. An application is entitled to call `set_start_method()` once at start-up, in its main module, because the application is the thing that knows its own deployment. A library, a framework plug-in, or any code that might be imported into someone else's process should take a context — ideally accepting one as a parameter with a sensible default — and never touch the global. `concurrent.futures.ProcessPoolExecutor` follows the same convention with its `mp_context` parameter, which is how you get a spawn-based executor without changing anything process-wide. ### Practical constraints Contexts are not interchangeable in use. A queue or lock created from one context should be used with processes from the same context; mixing them is unsupported and the failure mode is confusing rather than immediate. `get_start_method()` reports the current default and, by default, initialises it if it has not been set yet — so reading it can itself fix the default in place. `get_all_start_methods()` tells you what the platform supports, which is how portable code discovers that `fork` and `forkserver` are absent on Windows. Finally, none of this changes the other requirements: a context bound to `spawn` or `forkserver` still needs the `__main__` guard and still needs picklable arguments. Choosing a context locally makes the choice explicit and reviewable; it does not make the choice free. ### Reading the setting without fixing it `get_start_method()` takes an `allow_none` argument. Called plainly it initialises the default if nothing has chosen one yet and returns the name, which means a diagnostic print can itself pin the default in place before your application gets to `set_start_method()` — and the subsequent `set_start_method()` call then raises `RuntimeError`. Passing `allow_none=True` returns `None` instead of forcing initialisation, which is what you want in logging or introspection code. This is a small but real trap in start-up code that reports its configuration before applying it. ### Testing benefit Binding a context locally also makes the choice testable. A component that accepts a context as a parameter can be exercised under `spawn` and under `fork` in the same test session, which is how you find out that it silently depended on inherited module state — a discovery worth making in a test rather than after a platform default changes underneath the service.
- What happens if set_start_method is called twice?The second call raises `RuntimeError`, because the start method is meant to be fixed once before any process is created. Passing `force=True` suppresses that and overrides the previous setting — which is exactly why library code should not call it: it lets one component silently change a whole-program property another component depended on.
- How do you get a spawn-based ProcessPoolExecutor without touching the global default?Pass a context to it: build `mp.get_context("spawn")` and hand it to `concurrent.futures.ProcessPoolExecutor` through its `mp_context` parameter. The executor then creates its workers with that start method while the rest of the process keeps whatever default it had.
- Can objects from two different contexts be mixed?They should not be. A queue, lock or event created from one context is intended for processes from that same context; combining them is unsupported and fails in ways that are hard to read. If a component needs a specific start method, it should create all of its synchronisation and communication objects from its own context.
saying these in an interview costs you the question
- Calls set_start_method from library or module import code
- Uses force=True to get past the RuntimeError
- Thinks get_context changes the process default
- Mixes queues and processes from different contexts
- Assumes the ambient default is the same everywhere