File: threadsafety.rst

package info (click to toggle)
python3.14 3.14.3-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 167,092 kB
  • sloc: python: 759,022; ansic: 718,747; xml: 31,250; sh: 5,984; cpp: 4,093; makefile: 2,011; objc: 787; lisp: 502; javascript: 136; asm: 75; csh: 12
file content (606 lines) | stat: -rw-r--r-- 20,045 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
.. _threadsafety:

************************
Thread Safety Guarantees
************************

This page documents thread-safety guarantees for built-in types in Python's
free-threaded build. The guarantees described here apply when using Python with
the :term:`GIL` disabled (free-threaded mode). When the GIL is enabled, most
operations are implicitly serialized.

For general guidance on writing thread-safe code in free-threaded Python, see
:ref:`freethreading-python-howto`.


.. _threadsafety-levels:

Thread safety levels
====================

The C API documentation uses the following levels to describe the thread
safety guarantees of each function. The levels are listed from least to
most safe.

.. _threadsafety-level-incompatible:

Incompatible
------------

A function or operation that cannot be made safe for concurrent use even
with external synchronization. Incompatible code typically accesses
global state in an unsynchronized way and must only be called from a single
thread throughout the program's lifetime.

Example: a function that modifies process-wide state such as signal handlers
or environment variables, where concurrent calls from any threads, even with
external locking, can conflict with the runtime or other libraries.

.. _threadsafety-level-compatible:

Compatible
----------

A function or operation that is safe to call from multiple threads
*provided* the caller supplies appropriate external synchronization, for
example by holding a :term:`lock` for the duration of each call. Without
such synchronization, concurrent calls may produce :term:`race conditions
<race condition>` or :term:`data races <data race>`.

Example: a function that reads from or writes to an object whose internal
state is not protected by a lock. Callers must ensure that no two threads
access the same object at the same time.

.. _threadsafety-level-distinct:

Safe on distinct objects
------------------------

A function or operation that is safe to call from multiple threads without
external synchronization, as long as each thread operates on a **different**
object. Two threads may call the function at the same time, but they must
not pass the same object (or objects that share underlying state) as
arguments.

Example: a function that modifies fields of a struct using non-atomic
writes. Two threads can each call the function on their own struct
instance safely, but concurrent calls on the *same* instance require
external synchronization.

.. _threadsafety-level-shared:

Safe on shared objects
----------------------

A function or operation that is safe for concurrent use on the **same**
object. The implementation uses internal synchronization (such as
:term:`per-object locks <per-object lock>` or
:ref:`critical sections <python-critical-section-api>`) to protect shared
mutable state, so callers do not need to supply their own locking.

Example: :c:func:`PyList_GetItemRef` can be called from multiple threads on the
same :c:type:`PyListObject` - it uses internal synchronization to serialize
access.

.. _threadsafety-level-atomic:

Atomic
------

A function or operation that appears :term:`atomic <atomic operation>` with
respect to other threads - it executes instantaneously from the perspective
of other threads. This is the strongest form of thread safety.

Example: :c:func:`PyMutex_IsLocked` performs an atomic read of the mutex
state and can be called from any thread at any time.


.. _thread-safety-list:

Thread safety for list objects
==============================

Reading a single element from a :class:`list` is
:term:`atomic <atomic operation>`:

.. code-block::
   :class: good

   lst[i]   # list.__getitem__

The following methods traverse the list and use :term:`atomic <atomic operation>`
reads of each item to perform their function. That means that they may
return results affected by concurrent modifications:

.. code-block::
   :class: maybe

   item in lst
   lst.index(item)
   lst.count(item)

All of the above operations avoid acquiring :term:`per-object locks
<per-object lock>`. They do not block concurrent modifications. Other
operations that hold a lock will not block these from observing intermediate
states.

All other operations from here on block using the :term:`per-object lock`.

Writing a single item via ``lst[i] = x`` is safe to call from multiple
threads and will not corrupt the list.

The following operations return new objects and appear
:term:`atomic <atomic operation>` to other threads:

.. code-block::
   :class: good

   lst1 + lst2    # concatenates two lists into a new list
   x * lst        # repeats lst x times into a new list
   lst.copy()     # returns a shallow copy of the list

The following methods that only operate on a single element with no shifting
required are :term:`atomic <atomic operation>`:

.. code-block::
   :class: good

   lst.append(x)  # append to the end of the list, no shifting required
   lst.pop()      # pop element from the end of the list, no shifting required

The :meth:`~list.clear` method is also :term:`atomic <atomic operation>`.
Other threads cannot observe elements being removed.

The :meth:`~list.sort` method is not :term:`atomic <atomic operation>`.
Other threads cannot observe intermediate states during sorting, but the
list appears empty for the duration of the sort.

The following operations may allow :term:`lock-free` operations to observe
intermediate states since they modify multiple elements in place:

.. code-block::
   :class: maybe

   lst.insert(idx, item)  # shifts elements
   lst.pop(idx)           # idx not at the end of the list, shifts elements
   lst *= x               # copies elements in place

The :meth:`~list.remove` method may allow concurrent modifications since
element comparison may execute arbitrary Python code (via
:meth:`~object.__eq__`).

:meth:`~list.extend` is safe to call from multiple threads.  However, its
guarantees depend on the iterable passed to it. If it is a :class:`list`, a
:class:`tuple`, a :class:`set`, a :class:`frozenset`, a :class:`dict` or a
:ref:`dictionary view object <dict-views>` (but not their subclasses), the
``extend`` operation is safe from concurrent modifications to the iterable.
Otherwise, an iterator is created which can be concurrently modified by
another thread.  The same applies to inplace concatenation of a list with
other iterables when using ``lst += iterable``.

Similarly, assigning to a list slice with ``lst[i:j] = iterable`` is safe
to call from multiple threads, but ``iterable`` is only locked when it is
also a :class:`list` (but not its subclasses).

Operations that involve multiple accesses, as well as iteration, are never
atomic. For example:

.. code-block::
   :class: bad

   # NOT atomic: read-modify-write
   lst[i] = lst[i] + 1

   # NOT atomic: check-then-act
   if lst:
       item = lst.pop()

   # NOT thread-safe: iteration while modifying
   for item in lst:
       process(item)  # another thread may modify lst

Consider external synchronization when sharing :class:`list` instances
across threads.


.. _thread-safety-dict:

Thread safety for dict objects
==============================

Creating a dictionary with the :class:`dict` constructor is atomic when the
argument to it is a :class:`dict` or a :class:`tuple`. When using the
:meth:`dict.fromkeys` method, dictionary creation is atomic when the
argument is a :class:`dict`, :class:`tuple`, :class:`set` or
:class:`frozenset`.

The following operations and functions are :term:`lock-free` and
:term:`atomic <atomic operation>`.

.. code-block::
   :class: good

   d[key]       # dict.__getitem__
   d.get(key)   # dict.get
   key in d     # dict.__contains__
   len(d)       # dict.__len__

All other operations from here on hold the :term:`per-object lock`.

Writing or removing a single item is safe to call from multiple threads
and will not corrupt the dictionary:

.. code-block::
   :class: good

   d[key] = value        # write
   del d[key]            # delete
   d.pop(key)            # remove and return
   d.popitem()           # remove and return last item
   d.setdefault(key, v)  # insert if missing

These operations may compare keys using :meth:`~object.__eq__`, which can
execute arbitrary Python code. During such comparisons, the dictionary may
be modified by another thread. For built-in types like :class:`str`,
:class:`int`, and :class:`float`, that implement :meth:`~object.__eq__` in C,
the underlying lock is not released during comparisons and this is not a
concern.

The following operations return new objects and hold the :term:`per-object lock`
for the duration of the operation:

.. code-block::
   :class: good

   d.copy()      # returns a shallow copy of the dictionary
   d | other     # merges two dicts into a new dict
   d.keys()      # returns a new dict_keys view object
   d.values()    # returns a new dict_values view object
   d.items()     # returns a new dict_items view object

The :meth:`~dict.clear` method holds the lock for its duration. Other
threads cannot observe elements being removed.

The following operations lock both dictionaries. For :meth:`~dict.update`
and ``|=``, this applies only when the other operand is a :class:`dict`
that uses the standard dict iterator (but not subclasses that override
iteration). For equality comparison, this applies to :class:`dict` and
its subclasses:

.. code-block::
   :class: good

   d.update(other_dict)  # both locked when other_dict is a dict
   d |= other_dict       # both locked when other_dict is a dict
   d == other_dict       # both locked for dict and subclasses

All comparison operations also compare values using :meth:`~object.__eq__`,
so for non-built-in types the lock may be released during comparison.

:meth:`~dict.fromkeys` locks both the new dictionary and the iterable
when the iterable is exactly a :class:`dict`, :class:`set`, or
:class:`frozenset` (not subclasses):

.. code-block::
   :class: good

   dict.fromkeys(a_dict)      # locks both
   dict.fromkeys(a_set)       # locks both
   dict.fromkeys(a_frozenset) # locks both

When updating from a non-dict iterable, only the target dictionary is
locked. The iterable may be concurrently modified by another thread:

.. code-block::
   :class: maybe

   d.update(iterable)        # iterable is not a dict: only d locked
   d |= iterable             # iterable is not a dict: only d locked
   dict.fromkeys(iterable)   # iterable is not a dict/set/frozenset: only result locked

Operations that involve multiple accesses, as well as iteration, are never
atomic:

.. code-block::
   :class: bad

   # NOT atomic: read-modify-write
   d[key] = d[key] + 1

   # NOT atomic: check-then-act (TOCTOU)
   if key in d:
       del d[key]

   # NOT thread-safe: iteration while modifying
   for key, value in d.items():
       process(key)  # another thread may modify d

To avoid time-of-check to time-of-use (TOCTOU) issues, use atomic
operations or handle exceptions:

.. code-block::
   :class: good

   # Use pop() with default instead of check-then-delete
   d.pop(key, None)

   # Or handle the exception
   try:
       del d[key]
   except KeyError:
       pass

To safely iterate over a dictionary that may be modified by another
thread, iterate over a copy:

.. code-block::
   :class: good

   # Make a copy to iterate safely
   for key, value in d.copy().items():
       process(key)

Consider external synchronization when sharing :class:`dict` instances
across threads.


.. _thread-safety-set:

Thread safety for set objects
==============================

The :func:`len` function is lock-free and :term:`atomic <atomic operation>`.

The following read operation is lock-free. It does not block concurrent
modifications and may observe intermediate states from operations that
hold the per-object lock:

.. code-block::
   :class: good

   elem in s    # set.__contains__

This operation may compare elements using :meth:`~object.__eq__`, which can
execute arbitrary Python code. During such comparisons, the set may be
modified by another thread. For built-in types like :class:`str`,
:class:`int`, and :class:`float`, :meth:`!__eq__` does not release the
underlying lock during comparisons and this is not a concern.

All other operations from here on hold the per-object lock.

Adding or removing a single element is safe to call from multiple threads
and will not corrupt the set:

.. code-block::
   :class: good

   s.add(elem)      # add element
   s.remove(elem)   # remove element, raise if missing
   s.discard(elem)  # remove element if present
   s.pop()          # remove and return arbitrary element

These operations also compare elements, so the same :meth:`~object.__eq__`
considerations as above apply.

The :meth:`~set.copy` method returns a new object and holds the per-object lock
for the duration so that it is always atomic.

The :meth:`~set.clear` method holds the lock for its duration. Other
threads cannot observe elements being removed.

The following operations only accept :class:`set` or :class:`frozenset`
as operands and always lock both objects:

.. code-block::
   :class: good

   s |= other                   # other must be set/frozenset
   s &= other                   # other must be set/frozenset
   s -= other                   # other must be set/frozenset
   s ^= other                   # other must be set/frozenset
   s & other                    # other must be set/frozenset
   s | other                    # other must be set/frozenset
   s - other                    # other must be set/frozenset
   s ^ other                    # other must be set/frozenset

:meth:`set.update`, :meth:`set.union`, :meth:`set.intersection` and
:meth:`set.difference` can take multiple iterables as arguments. They all
iterate through all the passed iterables and do the following:

   * :meth:`set.update` and :meth:`set.union` lock both objects only when
      the other operand is a :class:`set`, :class:`frozenset`, or :class:`dict`.
   * :meth:`set.intersection` and :meth:`set.difference` always try to lock
      all objects.

:meth:`set.symmetric_difference` tries to lock both objects.

The update variants of the above methods also have some differences between
them:

   * :meth:`set.difference_update` and :meth:`set.intersection_update` try
      to lock all objects one-by-one.
   * :meth:`set.symmetric_difference_update` only locks the arguments if it is
      of type :class:`set`, :class:`frozenset`, or :class:`dict`.

The following methods always try to lock both objects:

.. code-block::
   :class: good

   s.isdisjoint(other)          # both locked
   s.issubset(other)            # both locked
   s.issuperset(other)          # both locked

Operations that involve multiple accesses, as well as iteration, are never
atomic:

.. code-block::
   :class: bad

   # NOT atomic: check-then-act
   if elem in s:
         s.remove(elem)

   # NOT thread-safe: iteration while modifying
   for elem in s:
         process(elem)  # another thread may modify s

Consider external synchronization when sharing :class:`set` instances
across threads.  See :ref:`freethreading-python-howto` for more information.


.. _thread-safety-bytearray:

Thread safety for bytearray objects
===================================

   The :func:`len` function is lock-free and :term:`atomic <atomic operation>`.

   Concatenation and comparisons use the buffer protocol, which prevents
   resizing but does not hold the per-object lock. These operations may
   observe intermediate states from concurrent modifications:

   .. code-block::
      :class: maybe

      ba + other    # may observe concurrent writes
      ba == other   # may observe concurrent writes
      ba < other    # may observe concurrent writes

   All other operations from here on hold the per-object lock.

   Reading a single element or slice is safe to call from multiple threads:

   .. code-block::
      :class: good

      ba[i]        # bytearray.__getitem__
      ba[i:j]      # slice

   The following operations are safe to call from multiple threads and will
   not corrupt the bytearray:

   .. code-block::
      :class: good

      ba[i] = x         # write single byte
      ba[i:j] = values  # write slice
      ba.append(x)      # append single byte
      ba.extend(other)  # extend with iterable
      ba.insert(i, x)   # insert single byte
      ba.pop()          # remove and return last byte
      ba.pop(i)         # remove and return byte at index
      ba.remove(x)      # remove first occurrence
      ba.reverse()      # reverse in place
      ba.clear()        # remove all bytes

   Slice assignment locks both objects when *values* is a :class:`bytearray`:

   .. code-block::
      :class: good

      ba[i:j] = other_bytearray  # both locked

   The following operations return new objects and hold the per-object lock
   for the duration:

   .. code-block::
      :class: good

      ba.copy()     # returns a shallow copy
      ba * n        # repeat into new bytearray

   The membership test holds the lock for its duration:

   .. code-block::
      :class: good

      x in ba       # bytearray.__contains__

   All other bytearray methods (such as :meth:`~bytearray.find`,
   :meth:`~bytearray.replace`, :meth:`~bytearray.split`,
   :meth:`~bytearray.decode`, etc.) hold the per-object lock for their
   duration.

   Operations that involve multiple accesses, as well as iteration, are never
   atomic:

   .. code-block::
      :class: bad

      # NOT atomic: check-then-act
      if x in ba:
          ba.remove(x)

      # NOT thread-safe: iteration while modifying
      for byte in ba:
          process(byte)  # another thread may modify ba

   To safely iterate over a bytearray that may be modified by another
   thread, iterate over a copy:

   .. code-block::
      :class: good

      # Make a copy to iterate safely
      for byte in ba.copy():
          process(byte)

   Consider external synchronization when sharing :class:`bytearray` instances
   across threads.  See :ref:`freethreading-python-howto` for more information.


.. _thread-safety-memoryview:

Thread safety for memoryview objects
====================================

:class:`memoryview` objects provide access to the internal data of an
underlying object without copying. Thread safety depends on both the
memoryview itself and the underlying buffer exporter.

The memoryview implementation uses atomic operations to track its own
exports in the :term:`free-threaded build`. Creating and
releasing a memoryview are thread-safe. Attribute access (e.g.,
:attr:`~memoryview.shape`, :attr:`~memoryview.format`) reads fields that
are immutable for the lifetime of the memoryview, so concurrent reads
are safe as long as the memoryview has not been released.

However, the actual data accessed through the memoryview is owned by the
underlying object. Concurrent access to this data is only safe if the
underlying object supports it:

* For immutable objects like :class:`bytes`, concurrent reads through
  multiple memoryviews are safe.

* For mutable objects like :class:`bytearray`, reading and writing the
  same memory region from multiple threads without external
  synchronization is not safe and may result in data corruption.
  Note that even read-only memoryviews of mutable objects do not
  prevent data races if the underlying object is modified from
  another thread.

.. code-block::
   :class: bad

   # NOT safe: concurrent writes to the same buffer
   data = bytearray(1000)
   view = memoryview(data)
   # Thread 1: view[0:500] = b'x' * 500
   # Thread 2: view[0:500] = b'y' * 500

.. code-block::
   :class: good

   # Safe: use a lock for concurrent access
   import threading
   lock = threading.Lock()
   data = bytearray(1000)
   view = memoryview(data)

   with lock:
       view[0:500] = b'x' * 500

Resizing or reallocating the underlying object (such as calling
:meth:`bytearray.resize`) while a memoryview is exported raises
:exc:`BufferError`. This is enforced regardless of threading.