Skip to content

Commit 75d5664

Browse files
committed
gh-142518: Move thread safety sections into a new page
- Create a new page for thread safety notes for built-in types - Move thread safety notes for `list` into the new page - Move thread safety notes for `dict` into the new page
1 parent 3718f4b commit 75d5664

File tree

3 files changed

+269
-241
lines changed

3 files changed

+269
-241
lines changed

Doc/library/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ the `Python Package Index <https://pypi.org>`_.
4343
constants.rst
4444
stdtypes.rst
4545
exceptions.rst
46+
threadsafety.rst
4647

4748
text.rst
4849
binary.rst

Doc/library/stdtypes.rst

Lines changed: 6 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,111 +1441,10 @@ application).
14411441
list appear empty for the duration, and raises :exc:`ValueError` if it can
14421442
detect that the list has been mutated during a sort.
14431443

1444-
.. _thread-safety-list:
1445-
1446-
.. rubric:: Thread safety for list objects
1447-
1448-
Reading a single element from a :class:`list` is
1449-
:term:`atomic <atomic operation>`:
1450-
1451-
.. code-block::
1452-
:class: green
1453-
1454-
lst[i] # list.__getitem__
1455-
1456-
The following methods traverse the list and use :term:`atomic <atomic operation>`
1457-
reads of each item to perform their function. That means that they may
1458-
return results affected by concurrent modifications:
1459-
1460-
.. code-block::
1461-
:class: maybe
1462-
1463-
item in lst
1464-
lst.index(item)
1465-
lst.count(item)
1466-
1467-
All of the above operations avoid acquiring :term:`per-object locks
1468-
<per-object lock>`. They do not block concurrent modifications. Other
1469-
operations that hold a lock will not block these from observing intermediate
1470-
states.
1471-
1472-
All other operations from here on block using the :term:`per-object lock`.
1473-
1474-
Writing a single item via ``lst[i] = x`` is safe to call from multiple
1475-
threads and will not corrupt the list.
1476-
1477-
The following operations return new objects and appear
1478-
:term:`atomic <atomic operation>` to other threads:
1479-
1480-
.. code-block::
1481-
:class: good
1482-
1483-
lst1 + lst2 # concatenates two lists into a new list
1484-
x * lst # repeats lst x times into a new list
1485-
lst.copy() # returns a shallow copy of the list
1486-
1487-
The following methods that only operate on a single element with no shifting
1488-
required are :term:`atomic <atomic operation>`:
1489-
1490-
.. code-block::
1491-
:class: good
1492-
1493-
lst.append(x) # append to the end of the list, no shifting required
1494-
lst.pop() # pop element from the end of the list, no shifting required
1495-
1496-
The :meth:`~list.clear` method is also :term:`atomic <atomic operation>`.
1497-
Other threads cannot observe elements being removed.
1498-
1499-
The :meth:`~list.sort` method is not :term:`atomic <atomic operation>`.
1500-
Other threads cannot observe intermediate states during sorting, but the
1501-
list appears empty for the duration of the sort.
1502-
1503-
The following operations may allow :term:`lock-free` operations to observe
1504-
intermediate states since they modify multiple elements in place:
1505-
1506-
.. code-block::
1507-
:class: maybe
1508-
1509-
lst.insert(idx, item) # shifts elements
1510-
lst.pop(idx) # idx not at the end of the list, shifts elements
1511-
lst *= x # copies elements in place
1512-
1513-
The :meth:`~list.remove` method may allow concurrent modifications since
1514-
element comparison may execute arbitrary Python code (via
1515-
:meth:`~object.__eq__`).
1516-
1517-
:meth:`~list.extend` is safe to call from multiple threads. However, its
1518-
guarantees depend on the iterable passed to it. If it is a :class:`list`, a
1519-
:class:`tuple`, a :class:`set`, a :class:`frozenset`, a :class:`dict` or a
1520-
:ref:`dictionary view object <dict-views>` (but not their subclasses), the
1521-
``extend`` operation is safe from concurrent modifications to the iterable.
1522-
Otherwise, an iterator is created which can be concurrently modified by
1523-
another thread. The same applies to inplace concatenation of a list with
1524-
other iterables when using ``lst += iterable``.
1525-
1526-
Similarly, assigning to a list slice with ``lst[i:j] = iterable`` is safe
1527-
to call from multiple threads, but ``iterable`` is only locked when it is
1528-
also a :class:`list` (but not its subclasses).
1529-
1530-
Operations that involve multiple accesses, as well as iteration, are never
1531-
atomic. For example:
1532-
1533-
.. code-block::
1534-
:class: bad
1535-
1536-
# NOT atomic: read-modify-write
1537-
lst[i] = lst[i] + 1
1538-
1539-
# NOT atomic: check-then-act
1540-
if lst:
1541-
item = lst.pop()
1542-
1543-
# NOT thread-safe: iteration while modifying
1544-
for item in lst:
1545-
process(item) # another thread may modify lst
1444+
.. seealso::
15461445

1547-
Consider external synchronization when sharing :class:`list` instances
1548-
across threads. See :ref:`freethreading-python-howto` for more information.
1446+
For detailed information on thread-safety guarantees for :class:`list`
1447+
objects, see :ref:`thread-safety-list`.
15491448

15501449

15511450
.. _typesseq-tuple:
@@ -5593,144 +5492,10 @@ can be used interchangeably to index the same dictionary entry.
55935492
of a :class:`dict`.
55945493

55955494

5596-
.. _thread-safety-dict:
5597-
5598-
.. rubric:: Thread safety for dict objects
5599-
5600-
Creating a dictionary with the :class:`dict` constructor is atomic when the
5601-
argument to it is a :class:`dict` or a :class:`tuple`. When using the
5602-
:meth:`dict.fromkeys` method, dictionary creation is atomic when the
5603-
argument is a :class:`dict`, :class:`tuple`, :class:`set` or
5604-
:class:`frozenset`.
5605-
5606-
The following operations and functions are :term:`lock-free` and
5607-
:term:`atomic <atomic operation>`.
5608-
5609-
.. code-block::
5610-
:class: good
5611-
5612-
d[key] # dict.__getitem__
5613-
d.get(key) # dict.get
5614-
key in d # dict.__contains__
5615-
len(d) # dict.__len__
5616-
5617-
All other operations from here on hold the :term:`per-object lock`.
5618-
5619-
Writing or removing a single item is safe to call from multiple threads
5620-
and will not corrupt the dictionary:
5621-
5622-
.. code-block::
5623-
:class: good
5624-
5625-
d[key] = value # write
5626-
del d[key] # delete
5627-
d.pop(key) # remove and return
5628-
d.popitem() # remove and return last item
5629-
d.setdefault(key, v) # insert if missing
5630-
5631-
These operations may compare keys using :meth:`~object.__eq__`, which can
5632-
execute arbitrary Python code. During such comparisons, the dictionary may
5633-
be modified by another thread. For built-in types like :class:`str`,
5634-
:class:`int`, and :class:`float`, that implement :meth:`~object.__eq__` in C,
5635-
the underlying lock is not released during comparisons and this is not a
5636-
concern.
5637-
5638-
The following operations return new objects and hold the :term:`per-object lock`
5639-
for the duration of the operation:
5640-
5641-
.. code-block::
5642-
:class: good
5643-
5644-
d.copy() # returns a shallow copy of the dictionary
5645-
d | other # merges two dicts into a new dict
5646-
d.keys() # returns a new dict_keys view object
5647-
d.values() # returns a new dict_values view object
5648-
d.items() # returns a new dict_items view object
5649-
5650-
The :meth:`~dict.clear` method holds the lock for its duration. Other
5651-
threads cannot observe elements being removed.
5652-
5653-
The following operations lock both dictionaries. For :meth:`~dict.update`
5654-
and ``|=``, this applies only when the other operand is a :class:`dict`
5655-
that uses the standard dict iterator (but not subclasses that override
5656-
iteration). For equality comparison, this applies to :class:`dict` and
5657-
its subclasses:
5658-
5659-
.. code-block::
5660-
:class: good
5661-
5662-
d.update(other_dict) # both locked when other_dict is a dict
5663-
d |= other_dict # both locked when other_dict is a dict
5664-
d == other_dict # both locked for dict and subclasses
5665-
5666-
All comparison operations also compare values using :meth:`~object.__eq__`,
5667-
so for non-built-in types the lock may be released during comparison.
5668-
5669-
:meth:`~dict.fromkeys` locks both the new dictionary and the iterable
5670-
when the iterable is exactly a :class:`dict`, :class:`set`, or
5671-
:class:`frozenset` (not subclasses):
5672-
5673-
.. code-block::
5674-
:class: good
5675-
5676-
dict.fromkeys(a_dict) # locks both
5677-
dict.fromkeys(a_set) # locks both
5678-
dict.fromkeys(a_frozenset) # locks both
5679-
5680-
When updating from a non-dict iterable, only the target dictionary is
5681-
locked. The iterable may be concurrently modified by another thread:
5682-
5683-
.. code-block::
5684-
:class: maybe
5685-
5686-
d.update(iterable) # iterable is not a dict: only d locked
5687-
d |= iterable # iterable is not a dict: only d locked
5688-
dict.fromkeys(iterable) # iterable is not a dict/set/frozenset: only result locked
5689-
5690-
Operations that involve multiple accesses, as well as iteration, are never
5691-
atomic:
5692-
5693-
.. code-block::
5694-
:class: bad
5695-
5696-
# NOT atomic: read-modify-write
5697-
d[key] = d[key] + 1
5698-
5699-
# NOT atomic: check-then-act (TOCTOU)
5700-
if key in d:
5701-
del d[key]
5702-
5703-
# NOT thread-safe: iteration while modifying
5704-
for key, value in d.items():
5705-
process(key) # another thread may modify d
5706-
5707-
To avoid time-of-check to time-of-use (TOCTOU) issues, use atomic
5708-
operations or handle exceptions:
5709-
5710-
.. code-block::
5711-
:class: good
5712-
5713-
# Use pop() with default instead of check-then-delete
5714-
d.pop(key, None)
5715-
5716-
# Or handle the exception
5717-
try:
5718-
del d[key]
5719-
except KeyError:
5720-
pass
5721-
5722-
To safely iterate over a dictionary that may be modified by another
5723-
thread, iterate over a copy:
5724-
5725-
.. code-block::
5726-
:class: good
5727-
5728-
# Make a copy to iterate safely
5729-
for key, value in d.copy().items():
5730-
process(key)
5495+
.. seealso::
57315496

5732-
Consider external synchronization when sharing :class:`dict` instances
5733-
across threads. See :ref:`freethreading-python-howto` for more information.
5497+
For detailed information on thread-safety guarantees for :class:`dict`
5498+
objects, see :ref:`thread-safety-dict`.
57345499

57355500

57365501
.. _dict-views:

0 commit comments

Comments
 (0)