13
13
14
14
--------------
15
15
16
-
17
- Introduction
18
- ------------
19
-
20
16
The :mod: `!concurrent.interpreters ` module constructs higher-level
21
17
interfaces on top of the lower level :mod: `!_interpreters ` module.
22
18
23
- .. XXX Add references to the upcoming HOWTO docs in the seealso block.
19
+ The module is primarily meant to provide a basic API for managing
20
+ interpreters (AKA "subinterpreters") and running things in them.
21
+ Running mostly involves switching to an interpreter (in the current
22
+ thread) and calling a function in that execution context.
23
+
24
+ For concurrency, interpreters themselves (and this module) don't
25
+ provide much more than isolation, which on its own isn't useful.
26
+ Actual concurrency is available separately through
27
+ :mod: `threads <threading> ` See `below <interp-concurrency _>`_
24
28
25
29
.. seealso ::
26
30
31
+ :class: `~concurrent.futures.InterpreterPoolExecutor `
32
+ combines threads with interpreters in a familiar interface.
33
+
34
+ .. XXX Add references to the upcoming HOWTO docs in the seealso block.
35
+
27
36
:ref: `isolating-extensions-howto `
28
37
how to update an extension module to support multiple interpreters
29
38
@@ -41,18 +50,155 @@ interfaces on top of the lower level :mod:`!_interpreters` module.
41
50
Key details
42
51
-----------
43
52
44
- Before we dive into examples , there are a small number of details
53
+ Before we dive in further , there are a small number of details
45
54
to keep in mind about using multiple interpreters:
46
55
47
- * isolated, by default
56
+ * ` isolated < interp-isolation _>`_ , by default
48
57
* no implicit threads
49
58
* not all PyPI packages support use in multiple interpreters yet
50
59
51
60
.. XXX Are there other relevant details to list?
52
61
53
- In the context of multiple interpreters, "isolated" means that
54
- different interpreters do not share any state. In practice, there is some
55
- process-global data they all share, but that is managed by the runtime.
62
+
63
+ .. _interpreters-intro :
64
+
65
+ Introduction
66
+ ------------
67
+
68
+ An "interpreter" is effectively the execution context of the Python
69
+ runtime. It contains all of the state the runtime needs to execute
70
+ a program. This includes things like the import state and builtins.
71
+ (Each thread, even if there's only the main thread, has some extra
72
+ runtime state, in addition to the current interpreter, related to
73
+ the current exception and the bytecode eval loop.)
74
+
75
+ The concept and functionality of the interpreter have been a part of
76
+ Python since version 2.2, but the feature was only available through
77
+ the C-API and not well known, and the `isolation <interp-isolation _>`_
78
+ was relatively incomplete until version 3.12.
79
+
80
+ .. _interp-isolation :
81
+
82
+ Multiple Interpreters and Isolation
83
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84
+
85
+ A Python implementation may support using multiple interpreters in the
86
+ same process. CPython has this support. Each interpreter is
87
+ effectively isolated from the others (with a limited number of
88
+ carefully managed process-global exceptions to the rule).
89
+
90
+ That isolation is primarily useful as a strong separation between
91
+ distinct logical components of a program, where you want to have
92
+ careful control of how those components interact.
93
+
94
+ .. note ::
95
+
96
+ Interpreters in the same process can technically never be strictly
97
+ isolated from one another since there are few restrictions on memory
98
+ access within the same process. The Python runtime makes a best
99
+ effort at isolation but extension modules may easily violate that.
100
+ Therefore, do not use multiple interpreters in security-sensitive
101
+ situations, where they shouldn't have access to each other's data.
102
+
103
+ Running in an Interpreter
104
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
105
+
106
+ Running in a different interpreter involves switching to it in the
107
+ current thread and then calling some function. The runtime will
108
+ execute the function using the current interpreter's state. The
109
+ :mod: `!concurrent.interpreters ` module provides a basic API for
110
+ creating and managing interpreters, as well as the switch-and-call
111
+ operation.
112
+
113
+ No other threads are automatically started for the operation.
114
+ There is `a helper <interp-call-in-thread _>`_ for that though.
115
+ There is another dedicated helper for calling the builtin
116
+ :func: `exec ` in an interpreter.
117
+
118
+ When :func: `exec ` (or :func: `eval `) are called in an interpreter,
119
+ they run using the interpreter's :mod: `!__main__ ` module as the
120
+ "globals" namespace. The same is true for functions that aren't
121
+ associated with any module. This is the same as how scripts invoked
122
+ from the command-line run in the :mod: `!__main__ ` module.
123
+
124
+
125
+ .. _interp-concurrency :
126
+
127
+ Concurrency and Parallelism
128
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
129
+
130
+ As noted earlier, interpreters do not provide any concurrency
131
+ on their own. They strictly represent the isolated execution
132
+ context the runtime will use *in the current thread *. That isolation
133
+ makes them similar to processes, but they still enjoy in-process
134
+ efficiency, like threads.
135
+
136
+ All that said, interpreters do naturally support certain flavors of
137
+ concurrency, as a powerful side effect of that isolation.
138
+ There's a powerful side effect of that isolation. It enables a
139
+ different approach to concurrency than you can take with async or
140
+ threads. It's a similar concurrency model to CSP or the actor model,
141
+ a model which is relatively easy to reason about.
142
+
143
+ You can take advantage of that concurrency model in a single thread,
144
+ switching back and forth between interpreters, Stackless-style.
145
+ However, this model is more useful when you combine interpreters
146
+ with multiple threads. This mostly involves starting a new thread,
147
+ where you switch to another interpreter and run what you want there.
148
+
149
+ Each actual thread in Python, even if you're only running in the main
150
+ thread, has its own *current * execution context. Multiple threads can
151
+ use the same interpreter or different ones.
152
+
153
+ At a high level, you can think of the combination of threads and
154
+ interpreters as threads with opt-in sharing.
155
+
156
+ As a significant bonus, interpreters are sufficiently isolated that
157
+ they do not share the :term: `GIL `, which means combining threads with
158
+ multiple interpreters enables full multi-core parallelism.
159
+ (This has been the case since Python 3.12.)
160
+
161
+ Communication Between Interpreters
162
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
163
+
164
+ In practice, multiple interpreters are useful only if we have a way
165
+ to communicate between them. This usually involves some form of
166
+ message passing, but can even mean sharing data in some carefully
167
+ managed way.
168
+
169
+ With this in mind, the :mod: `!concurrent.interpreters ` module provides
170
+ a :class: `queue.Queue ` implementation, available through
171
+ :func: `create_queue `.
172
+
173
+ .. _interp-object-sharing :
174
+
175
+ "Sharing" Objects
176
+ ^^^^^^^^^^^^^^^^^
177
+
178
+ Any data actually shared between interpreters loses the thread-safety
179
+ provided by the :term: `GIL `. There are various options for dealing with
180
+ this in extension modules. However, from Python code the lack of
181
+ thread-safety means objects can't actually be shared, with a few
182
+ exceptions. Instead, a copy must be created, which means mutable
183
+ objects won't stay in sync.
184
+
185
+ By default, most objects are copied with :mod: `pickle ` when they are
186
+ passed to another interpreter. Nearly all of the immutable builtin
187
+ objects are either directly shared or copied efficiently. For example:
188
+
189
+ * :const: `None `
190
+ * :class: `bool ` (:const: `True ` and :const: `False `)
191
+ * :class: `bytes `
192
+ * :class: `str `
193
+ * :class: `int `
194
+ * :class: `float `
195
+ * :class: `tuple ` (of similarly supported objects)
196
+
197
+ There is a small number of Python types that actually share mutable
198
+ data between interpreters:
199
+
200
+ * :class: `memoryview `
201
+ * :class: `Queue `
56
202
57
203
58
204
Reference
@@ -73,12 +219,19 @@ This module defines the following functions:
73
219
.. function :: get_main()
74
220
75
221
Return an :class: `Interpreter ` object for the main interpreter.
222
+ This is the interpreter the runtime created to run the :term: `REPL `
223
+ or the script given at the command-line. It is usually the only one.
76
224
77
225
.. function :: create()
78
226
79
227
Initialize a new (idle) Python interpreter
80
228
and return a :class: `Interpreter ` object for it.
81
229
230
+ .. function :: create_queue()
231
+
232
+ Initialize a new cross-interpreter queue and return a :class: `Queue `
233
+ object for it.
234
+
82
235
83
236
Interpreter objects
84
237
^^^^^^^^^^^^^^^^^^^
@@ -94,7 +247,7 @@ Interpreter objects
94
247
95
248
(read-only)
96
249
97
- The interpreter's ID.
250
+ The underlying interpreter's ID.
98
251
99
252
.. attribute :: whence
100
253
@@ -113,8 +266,10 @@ Interpreter objects
113
266
114
267
.. method :: prepare_main(ns=None, **kwargs)
115
268
116
- Bind "shareable" objects in the interpreter's
117
- :mod: `!__main__ ` module.
269
+ Bind objects in the interpreter's :mod: `!__main__ ` module.
270
+
271
+ Some objects are actually shared and some are copied efficiently,
272
+ but most are copied via :mod: `pickle `. See :ref: `interp-object-sharing `.
118
273
119
274
.. method :: exec(code, /, dedent=True)
120
275
@@ -125,6 +280,8 @@ Interpreter objects
125
280
Return the result of calling running the given function in the
126
281
interpreter (in the current thread).
127
282
283
+ .. _interp-call-in-thread :
284
+
128
285
.. method :: call_in_thread(callable, /, *args, **kwargs)
129
286
130
287
Run the given function in the interpreter (in a new thread).
@@ -159,7 +316,36 @@ Exceptions
159
316
an object cannot be sent to another interpreter.
160
317
161
318
162
- .. XXX Add functions for communicating between interpreters.
319
+ Communicating Between Interpreters
320
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
321
+
322
+ .. class :: Queue(id)
323
+
324
+ A wrapper around a low-level, cross-interpreter queue, which
325
+ implements the :class: `queue.Queue ` interface. The underlying queue
326
+ can only be created through :func: `create_queue `.
327
+
328
+ Some objects are actually shared and some are copied efficiently,
329
+ but most are copied via :mod: `pickle `. See :ref: `interp-object-sharing `.
330
+
331
+ .. attribute :: id
332
+
333
+ (read-only)
334
+
335
+ The queue's ID.
336
+
337
+
338
+ .. exception :: QueueEmptyError
339
+
340
+ This exception, a subclass of :exc: `queue.Empty `, is raised from
341
+ :meth: `!Queue.get ` and :meth: `!Queue.get_nowait ` when the queue
342
+ is empty.
343
+
344
+ .. exception :: QueueFullError
345
+
346
+ This exception, a subclass of :exc: `queue.Full `, is raised from
347
+ :meth: `!Queue.put ` and :meth: `!Queue.put_nowait ` when the queue
348
+ is full.
163
349
164
350
165
351
Basic usage
@@ -184,6 +370,12 @@ Creating an interpreter and running code in it::
184
370
print('spam!')
185
371
"""))
186
372
373
+ def run(arg):
374
+ return arg
375
+
376
+ res = interp.call(run, 'spam!')
377
+ print(res)
378
+
187
379
def run():
188
380
print('spam!')
189
381
@@ -193,6 +385,3 @@ Creating an interpreter and running code in it::
193
385
194
386
t = interp.call_in_thread(run)
195
387
t.join()
196
-
197
-
198
- .. XXX Explain about object "sharing".
0 commit comments