Repository navigation
InterpreterPoolExecutor does not work as replacement of ThreadPoolExecutor. #136603
Description
Activity
cc @hugovk We may need to add label release blocker for 3.14
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errorstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Jul 13, 2025 I think this is because
do_callis treated as a stateful function that cannot be shared between multiple interpreters:$3 = {total = 14, locals = {total = 5, args = {total = 4, numposonly = 0, numposorkw = 4, numkwonly = 0, varargs = 0, varkwargs = 0}, numpure = 1, cells = {total = 0, numargs = 0, numothers = 0}, hidden = {total = 0, numpure = 0, numcells = 0}}, numfree = 0, unbound = {total = 9, globals = {total = 5, numglobal = 3, numbuiltin = 3, numunknown = 0}, numattrs = 4, numunknown = 0}}The
_interp_call_packfunction will check whether the function is stateless:
cpython/Modules/_interpretersmodule.c
Lines 467 to 488 in 42b251b
static int _interp_call_pack(PyThreadState *tstate, struct interp_call *call, PyObject *func, PyObject *args, PyObject *kwargs) { xidata_fallback_t fallback = _PyXIDATA_FULL_FALLBACK; assert(call->func == NULL); assert(call->args == NULL); assert(call->kwargs == NULL); // Handle the func. if (!PyCallable_Check(func)) { _PyErr_Format(tstate, PyExc_TypeError, "expected a callable, got %R", func); return -1; } if (_PyFunction_GetXIData(tstate, func, &call->_preallocated.func) < 0) { PyObject *exc = _PyErr_GetRaisedException(tstate); if (_PyPickle_GetXIData(tstate, func, &call->_preallocated.func) < 0) { _PyErr_SetRaisedException(tstate, exc); return -1; } Py_DECREF(exc); } _interp_call_pack -> _PyFunction_GetXIData -> _PyFunction_VerifyStateless -> _PyCode_VerifyStateless -> _PyCode_CheckNoExternalStatewhere
do_callfails due to:
Lines 2006 to 2008 in 42b251b
else if (counts->unbound.globals.numglobal > 0) { errmsg = "globals not supported"; } And the unbound vars seem to be:
['BaseException', 'interpreters', 'print', 'sys', 'traceback']@xuantengh
I am waiting for @ericsnowcurrently 's reply. If this is the intention, most people will treat InterpreterPoolExecutor as the same as ThreadPoolExecutor, but actual behavior does not seem to be so; it should be documented.
But if it is not intended, it should be resolved before officially releasing 3.14.Reacted by Xuanteng Huang and Mikhail EfimovJFTR: it works if we slightly rewrite this code:
import concurrent.futures from concurrent.futures import InterpreterPoolExecutor, ThreadPoolExecutor def calc_fib(n): def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) return fib(n) with InterpreterPoolExecutor(max_workers=5) as executor: futures = {executor.submit(calc_fib, n): n for n in range(10)} for future in concurrent.futures.as_completed(futures): n = futures[future] data = future.result() print(f"fib({n}): {data}")
As
fibaccesses the global scope to get itself it won't work. Thx to @efimov-mikhail for his example. I think we should consider adding some note to docs/howto about subinterpreters, since recursive functions are pretty commonly used for calculations that we want to parallelise.Reacted by Mikhail Efimov, Donghee Na, Xuanteng Huang and ClericPyI think we should consider adding some note to docs/howto about subinterpreters, since recursive functions are pretty commonly used for calculations that we want to parallelise.
Maybe I can file a PR for this?
There used to be a comment in concurrent.futures.rst for InterpreterPoolExecutor saying:
Functions defined in the main module cannot be pickled and thus cannot be used.This comment was removed in #133957
I think we should consider adding some note to docs/howto about subinterpreters, since recursive functions are pretty commonly used for calculations that we want to parallelise.
Maybe I can file a PR for this?
It would be great, but it totally needs prior discussion. I think we should add a big section about recipes with subinterpreters, because there are more problems, than just defining recursive functions. E.g. top-level imports don't work either for the same reason. We should define preferred way to handle all these cases (ofc the discussion should involve Eric Snow and others involved in subinterpreters). We need to open another issue (or a discuss.python.org topic, potentially) :)
Reacted by Mikhail Efimov and Xuanteng HuangMaybe we should start a d.p.o. thread on "How to teach subinterpreters" or something similar?
Can we just wait Eric's comment before do something?
Reacted by Stan Ulbrych, Mikhail Efimov and Ilya SolinI clarified the issue that I met at #136659 (comment)
I think that we can reduce the scope of the issue because it is okay to run from Python 3.14b4 with__name__ == '__main__'idiom.So it looks reasonable to close this issue and focus on #136659
cc @ericsnowcurrently
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
According to PEP-734, it should work as same as ThreadPoolExecutor, but it looks not.
Code
Execution (InterpreterPoolExecutor)
Execution (ThreadPoolExecutor)