Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 14 – Concurrency and parallelism: threads, processes, interpreters

Subinterpreters with concurrent.interpreters

Run isolated Python interpreters inside one process with concurrent.interpreters: create and call them, pass data through queues, and pool them.

  • Advanced
  • 24 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 9 of 24 minutes free
Free: One process, several interpreters · Creating an interpreter and calling into it · Threads, processes or interpreters · Check yourself. With a pass: 2 in-depth parts and 3 interview answers, about 15 minutes.

What you will learn

  • Create interpreters, run code in them and call functions in them
  • Pass data between interpreters with calls, queues and memoryviews
  • Run CPU-bound work in parallel on interpreters and measure what each one costs
  • Write a fallback to threads for Pythons without concurrent.interpreters

Before you start

What you’ll learn · Before you start

What you will learn

  • Create interpreters, run code in them and call functions in them
  • Pass data between interpreters with calls, queues and memoryviews
  • Run CPU-bound work in parallel on interpreters and measure what each one costs
  • Write a fallback to threads for Pythons without concurrent.interpreters

Before you start

On this page

Since Python 3.14, the concurrent.interpreters module lets one process run several interpreters, each a complete and isolated copy of the Python runtime with its own modules, its own global variables and its own GIL. Code in two interpreters can therefore compute on two cores at the same moment, as two processes would, while everything stays inside one process. Nothing is shared by default: arguments and results cross between interpreters as copies, cheap ones for strings, numbers and tuples and pickled ones for most other objects, and a queue carries data from one interpreter to another. An interpreter runs code only when a thread calls into it, so parallel work needs one thread per interpreter, which InterpreterPoolExecutor arranges for you. The 3.14 release notes list the present limits: slow start-up, extra memory for each interpreter, few ways to share objects, and many third-party extension modules that do not support it yet.

One process, several interpreters

One process holds a main interpreter and two subinterpreters, each with its own modules and GIL, linked by call() and a queue.One processMain interpreteryour script as __main__,its own modules and GILQueuemost objects copied,a few sharedInterpreter 1its own modules,globals and GILInterpreter 2its own modules,globals and GILcall(): arguments in,result out, as copiesput()get()

Several interpreters inside one process

Text description of the diagram

The diagram shows one process containing four boxes.

  • The main interpreter runs your script as __main__, with its own modules and its own GIL.
  • Interpreter 1 and interpreter 2 each have their own modules, their own global variables and their own GIL, so code in different interpreters can run on different cores at the same time when each runs in its own thread.
  • An arrow from the main interpreter to interpreter 1 is labelled call(): the arguments go in and the result comes back as copies, not as shared objects.
  • Interpreter 2 puts items on a queue, and the main interpreter gets them from it. A queue copies most objects; a few, such as a memoryview, share their data.

Each box in the diagram is a separate world. Importing a module in interpreter 1 does not import it in interpreter 2, and a change to sys.path or to a global variable in one is invisible in the others, because each has its own copy of sys, of builtins and even of __main__. Since Python 3.12 each interpreter also has its own GIL, which is what lets two of them compute at the same time. The isolation is the point of the design: two interpreters can only meet where your code passes something between them. It is not a security boundary, though. They share one address space, and the documentation warns against using them to keep code away from data it must not read.

Creating an interpreter and calling into it

interpreters.create() makes a new, idle interpreter. exec() runs source code in it, and call() runs a function in it and returns the result; both run in the current thread and return when the code is done:

One interpreter, created, run, called and closed Python · first_interpreter.py
"""Create an interpreter, run code in it, call a function in it, and see what it keeps to itself."""
import sys
import textwrap
from concurrent import interpreters

sys.stdout.reconfigure(line_buffering=True)  # every interpreter has its own sys.stdout: show each line at once
print(f"top of the file runs as {__name__}")
visits = []

SOURCE = textwrap.dedent("""
    from concurrent import interpreters
    print("exec() runs in", __name__, "of interpreter", interpreters.get_current().id, flush=True)
""")


def visit(who):
    """Record a visit in this interpreter's own copy of the list."""
    visits.append(who)
    return f"{who}: {len(visits)} visit(s) in this interpreter's list"


if __name__ == "__main__":
    print(visit("main"))
    interp = interpreters.create()
    interp.exec(SOURCE)
    print(interp.call(visit, "sub"))
    print(interp.call(visit, "sub"))
    print(visit("main"))
    print("interpreters now:", [i.id for i in interpreters.list_all()])
    interp.close()
    print("after close():", [i.id for i in interpreters.list_all()])

Output

top of the file runs as __main__
main: 1 visit(s) in this interpreter's list
exec() runs in __main__ of interpreter 1
top of the file runs as <fake __main__>
sub: 1 visit(s) in this interpreter's list
sub: 2 visit(s) in this interpreter's list
main: 2 visit(s) in this interpreter's list
interpreters now: [0, 1]
after close(): [0]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 first_interpreter.py

The exec() code ran in the new interpreter’s own __main__, as interpreter 1. The first call() showed something easy to miss: to run visit(), the new interpreter imported this script under the name <fake __main__>, so its top line ran a second time there. That is why the code that creates interpreters sits under if __name__ == "__main__":, exactly as with process pools. Each interpreter then counted visits in its own list: two in the main interpreter and two in interpreter 1, never four in one place. An interpreter keeps its modules and variables between calls, so the second call found the list that the first one had filled. close() destroyed the interpreter, leaving only the main one, 0.

Threads, processes or interpreters

Three ways to run Python code at once in Python 3.14 (default build)
Criterion ThreadsProcessesInterpreters
Python code on several cores no: one GILyesyes: a GIL each
Shared every objectnothingonly queues and memoryviews
Arguments passed as they arepickledcopied, often pickled
Cost of one more smallestlargestin between
Extension modules allallonly compatible ones
When to choose Waiting work, shared dataCPU work with any libraryCPU work in one process

Interpreters sit between threads and processes on every row. Several tools move data between them, each with one job. A queue from create_queue() carries objects from one interpreter to another. A memoryview shares the bytes of a buffer, so writes in one interpreter are seen in the other. prepare_main() binds values into an interpreter’s __main__ before you run code there. call_in_thread() runs a call in a new thread of its own, which is how several interpreters compute at once. An uncaught exception in another interpreter reaches you as ExecutionFailed. And InterpreterPoolExecutor puts all of it behind the executor interface of concurrent.futures.

Where interpreters are not available, the module is simply missing. WebAssembly builds of Python, such as the one behind the Run buttons of these lessons, have no concurrent.interpreters, and Python 3.13 and older have no public module for it at all. Code that must run everywhere tries the import and falls back to threads or processes.

Check yourself

Four questions on interpreters

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 Two interpreters in one process count primes on two cores at the same time. What makes that possible?

    Choose one answer.

    Show the answer to question 1

    Answer: Each interpreter has its own GIL, and each runs in its own thread

    Since Python 3.12 interpreters are isolated enough not to share a GIL. An interpreter runs code only in the thread that calls it, so parallel work needs one thread per interpreter, which is what call_in_thread() and InterpreterPoolExecutor arrange.

  2. Question 2 of 4 interp is an interpreter made with interpreters.create(). What does the last line print?

    Read the code, then choose one answer.

    def add_item(items):
        items.append("added")
        return len(items)
    
    basket = ["apple"]
    count = interp.call(add_item, basket)
    print(count, basket)
    Show the answer to question 2

    Answer: 2 ['apple']

    The list crosses into the other interpreter as a copy. add_item() appends to that copy, so it returns 2, while the main interpreter's basket still holds one item. Only a few types, such as memoryview and queues, share their data between interpreters.

  3. Question 3 of 4 Where and when does interp.exec(code) run the code?

    Choose one answer.

    Show the answer to question 3

    Answer: In the thread that calls exec(), in the interpreter's own __main__ module, and exec() returns when the code is done

    An interpreter has no thread of its own. exec() switches the calling thread into the interpreter, runs the code with that interpreter's __main__ as its globals, and switches back. call_in_thread() is the variant that starts a new thread.

  4. Question 4 of 4 Which of these is a current limit of subinterpreters that the Python 3.14 release notes list?

    Choose one answer.

    Show the answer to question 4

    Answer: Many third-party extension modules do not support multiple interpreters yet

    The release notes list slow start-up, extra memory for each interpreter, few ways to share objects, and many extension modules on PyPI that are not yet compatible; the standard library's own extension modules are.

Moving data between interpreters, with each rule shown Pro: opens with a Pro, Premium or Ultimate pass

Interpreters meet only where your code passes something between them. This part runs each way across, the copies, the shared buffer, the queue and the exceptions, and shows what refuses to cross.

In this part:

  • What a call copies, what it shares and what it refuses
  • Results through a queue, from an interpreter in its own thread
  • Errors across the boundary: ExecutionFailed and what it keeps

3 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interpreters in parallel: speed, start-up and memory, measured Pro: opens with a Pro, Premium or Ultimate pass

Interpreters compute in parallel inside one process, but each one costs time to create and memory to keep. This part measures both against threads and processes, and ends with code that works where interpreters are missing.

In this part:

  • Eight interpreters on eight threads against eight threads in one interpreter
  • What one more worker costs: thread, interpreter, process
  • Which modules load in a subinterpreter: a check you can run on your own dependencies
  • Exercise: count words on interpreters, or on threads where there are none
    • Count words on interpreters, or on threads where there are none

3 runnable examples (Python) · 1 exercise with sample tests · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Mid to senior · Warm-up

    What does it mean that interpreters created with concurrent.interpreters are isolated, and what is it not?

    Each interpreter has its own copy of the runtime's state: its own imported modules, its own sys, builtins and __main__, its own global variables and, since Python 3.12, its own GIL. Importing a module or changing a global in one interpreter changes nothing in another, and objects do not cross between them: calls and queues copy most values, and only a few types, such as queues and memoryviews, share their data. That isolation is what lets two interpreters run Python code on two cores at once. It is not a security boundary: all interpreters live in one process and one address space, and an extension module can reach across, so the documentation warns against using them to keep code away from data it must not read.

  2. Mid to senior

    A service scores incoming documents with a pure-Python model that takes about 400 ms of CPU per document, using a ProcessPoolExecutor of 8 workers. A colleague proposes InterpreterPoolExecutor instead. How do you decide, and what would you measure first?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    After moving a batch job to InterpreterPoolExecutor, every task fails with an ImportError saying a module "does not support loading in subinterpreters", although the same import works in the main interpreter. Explain why, and what your options are.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    Eight interpreters each need fast read access to a 2 GB table of numbers, and one of them must update a few entries every minute. Design how the data is held and shared, and say what can go wrong.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • concurrent.interpreters, new in Python 3.14, runs isolated interpreters inside one process, each with its own modules, globals and GIL.
  • exec() and call() run in the calling thread; call_in_thread() or InterpreterPoolExecutor gives parallel work.
  • Arguments and results cross as copies; queues and memoryviews are the few things that are shared.
  • Interpreters import your script again to find its functions, so creation code goes under the main guard.
  • Their current limits are start-up time, memory per interpreter and extension modules that do not support them yet.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.