|
| 1 | +.. _multiple-interpreters: |
| 2 | + |
| 3 | +Multiple interpreters |
| 4 | +********************* |
| 5 | + |
| 6 | +If your process uses multiple interpreters (for example, using the `concurrent.interpreters` module |
| 7 | +or a `concurrent.futures.InterpreterPoolExecutor`), PyStack will show which interpreter each stack |
| 8 | +is associated with, and will even show where a thread switches from one interpreter to another. |
| 9 | + |
| 10 | +.. note:: |
| 11 | + This feature only works for processes running Python 3.11 or newer. |
| 12 | + |
| 13 | +Example program with multiple interpreters |
| 14 | +========================================== |
| 15 | + |
| 16 | +For instance, given this Python 3.14 program:: |
| 17 | + |
| 18 | + import os |
| 19 | + import signal |
| 20 | + from concurrent.futures import InterpreterPoolExecutor |
| 21 | + |
| 22 | + |
| 23 | + def interpreter1_body(read_fd): |
| 24 | + print("Hello from interpreter 1!") |
| 25 | + open(read_fd, closefd=False).read() |
| 26 | + print("Goodbye from interpreter 1!") |
| 27 | + |
| 28 | + |
| 29 | + def interpreter2_body(read_fd): |
| 30 | + print("Greetings from interpreter 2!") |
| 31 | + open(read_fd, closefd=False).read() |
| 32 | + print("Farewell from interpreter 2!") |
| 33 | + |
| 34 | + |
| 35 | + read_fd, write_fd = os.pipe() |
| 36 | + |
| 37 | + with InterpreterPoolExecutor(max_workers=2) as executor: |
| 38 | + executor.submit(interpreter1_body, read_fd) |
| 39 | + executor.submit(interpreter2_body, read_fd) |
| 40 | + |
| 41 | + try: |
| 42 | + signal.pause() |
| 43 | + except KeyboardInterrupt: |
| 44 | + print("\nCtrl-C received, signaling workers to stop...") |
| 45 | + os.close(write_fd) |
| 46 | + os.close(read_fd) |
| 47 | + |
| 48 | +If you run it until it pauses and then analyze it with PyStack, you will see output like this:: |
| 49 | + |
| 50 | + $ pystack remote 392462 |
| 51 | + Traceback for thread 392464 (InterpreterPool) (most recent call last): |
| 52 | + In the main interpreter [] |
| 53 | + (Python) File "/usr/lib/python3.14/threading.py", line 1044, in _bootstrap |
| 54 | + self._bootstrap_inner() |
| 55 | + (Python) File "/usr/lib/python3.14/threading.py", line 1082, in _bootstrap_inner |
| 56 | + self._context.run(self.run) |
| 57 | + (Python) File "/usr/lib/python3.14/threading.py", line 1024, in run |
| 58 | + self._target(*self._args, **self._kwargs) |
| 59 | + (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 119, in _worker |
| 60 | + work_item.run(ctx) |
| 61 | + (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 86, in run |
| 62 | + result = ctx.run(self.task) |
| 63 | + (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 84, in run |
| 64 | + return self.interp.call(do_call, self.results, *task) |
| 65 | + (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 238, in call |
| 66 | + return self._call(callable, args, kwargs) |
| 67 | + (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 220, in _call |
| 68 | + res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True) |
| 69 | + In interpreter 2 [] |
| 70 | + (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 11, in do_call |
| 71 | + return func(*args, **kwargs) |
| 72 | + (Python) File "/tmp/interpreter_demo.py", line 14, in interpreter2_body |
| 73 | + open(read_fd, closefd=False).read() |
| 74 | + |
| 75 | + Traceback for thread 392463 (InterpreterPool) (most recent call last): |
| 76 | + In the main interpreter [] |
| 77 | + (Python) File "/usr/lib/python3.14/threading.py", line 1044, in _bootstrap |
| 78 | + self._bootstrap_inner() |
| 79 | + (Python) File "/usr/lib/python3.14/threading.py", line 1082, in _bootstrap_inner |
| 80 | + self._context.run(self.run) |
| 81 | + (Python) File "/usr/lib/python3.14/threading.py", line 1024, in run |
| 82 | + self._target(*self._args, **self._kwargs) |
| 83 | + (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 119, in _worker |
| 84 | + work_item.run(ctx) |
| 85 | + (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 86, in run |
| 86 | + result = ctx.run(self.task) |
| 87 | + (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 84, in run |
| 88 | + return self.interp.call(do_call, self.results, *task) |
| 89 | + (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 238, in call |
| 90 | + return self._call(callable, args, kwargs) |
| 91 | + (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 220, in _call |
| 92 | + res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True) |
| 93 | + In interpreter 1 [] |
| 94 | + (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 11, in do_call |
| 95 | + return func(*args, **kwargs) |
| 96 | + (Python) File "/tmp/interpreter_demo.py", line 8, in interpreter1_body |
| 97 | + open(read_fd, closefd=False).read() |
| 98 | + |
| 99 | + Traceback for thread 392462 (python3.14) (most recent call last): |
| 100 | + In the main interpreter [] |
| 101 | + (Python) File "/tmp/interpreter_demo.py", line 25, in <module> |
| 102 | + signal.pause() |
| 103 | + |
| 104 | +Note that the output now shows which interpreter each thread is running in, and also shows the |
| 105 | +transition from the main interpreter to the subinterpreters. The GIL status is shown separately for |
| 106 | +each interpreter, as a thread may hold the GIL for one interpreter but not another. Likewise, the GC |
| 107 | +status is shown separately, since a thread may be running a GC cycle in one interpreter but not |
| 108 | +another. |
0 commit comments