Skip to content

Commit 96bc4d4

Browse files
committed
gh-142518: Document thread-safety guarantees of set objects
1 parent 80b2b88 commit 96bc4d4

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

Doc/library/stdtypes.rst

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5209,6 +5209,112 @@ Note, the *elem* argument to the :meth:`~object.__contains__`,
52095209
:meth:`~set.discard` methods may be a set. To support searching for an equivalent
52105210
frozenset, a temporary one is created from *elem*.
52115211

5212+
.. admonition:: Thread safety
5213+
5214+
The :func:`len` function is lock-free and :term:`atomic <atomic operation>`.
5215+
5216+
The following read operation is lock-free. It does not block concurrent
5217+
modifications and may observe intermediate states from operations that
5218+
hold the per-object lock:
5219+
5220+
.. code-block::
5221+
:class: good
5222+
5223+
elem in s # set.__contains__
5224+
5225+
This operation may compare elements using :meth:`~object.__eq__`, which can
5226+
execute arbitrary Python code. During such comparisons, the set may be
5227+
modified by another thread. For built-in types like :class:`str`,
5228+
:class:`int`, and :class:`float`, :meth:`!__eq__` does not release the
5229+
underlying lock during comparisons and this is not a concern.
5230+
5231+
All other operations from here on hold the per-object lock.
5232+
5233+
Adding or removing a single element is safe to call from multiple threads
5234+
and will not corrupt the set:
5235+
5236+
.. code-block::
5237+
:class: good
5238+
5239+
s.add(elem) # add element
5240+
s.remove(elem) # remove element, raise if missing
5241+
s.discard(elem) # remove element if present
5242+
s.pop() # remove and return arbitrary element
5243+
5244+
These operations also compare elements, so the same :meth:`~object.__eq__`
5245+
considerations as above apply.
5246+
5247+
The following operations return new objects and hold the per-object lock
5248+
for the duration:
5249+
5250+
.. code-block::
5251+
:class: good
5252+
5253+
s.copy() # returns a shallow copy
5254+
5255+
The :meth:`~set.clear` method holds the lock for its duration. Other
5256+
threads cannot observe elements being removed.
5257+
5258+
The following operations only accept :class:`set` or :class:`frozenset`
5259+
as operands and always lock both objects:
5260+
5261+
.. code-block::
5262+
:class: good
5263+
5264+
s |= other # other must be set/frozenset
5265+
s &= other # other must be set/frozenset
5266+
s -= other # other must be set/frozenset
5267+
s ^= other # other must be set/frozenset
5268+
s & other # other must be set/frozenset
5269+
s | other # other must be set/frozenset
5270+
s - other # other must be set/frozenset
5271+
s ^ other # other must be set/frozenset
5272+
5273+
:meth:`set.update`, :meth:`set.union`, :meth:`set.intersection` and
5274+
:meth:`set.difference` can take multiple iterables as arguments. They all
5275+
iterate through all the passed iterables and do the following:
5276+
5277+
* :meth:`set.update` and :meth:`set.union` lock both objects only when
5278+
the other operand is a :class:`set`, :class:`frozenset`, or :class:`dict`.
5279+
* :meth:`set.intersection` and :meth:`set.difference` always try to lock
5280+
all objects.
5281+
5282+
:meth:`set.symmetric_difference` tries to lock both objects.
5283+
5284+
The update variants of the above methods also have some differences between
5285+
them:
5286+
5287+
* :meth:`set.difference_update` and :meth:`set.intersection_update` try
5288+
to lock all objects.
5289+
* :meth:`set.symmetric_difference_update` only lock the argument if it is
5290+
of type :class:`set`, :class:`frozenset`, or :class:`dict`.
5291+
5292+
The following methods always try to lock both objects:
5293+
5294+
.. code-block::
5295+
:class: good
5296+
5297+
s.isdisjoint(other) # both locked
5298+
s.issubset(other) # both locked
5299+
s.issuperset(other) # both locked
5300+
5301+
Operations that involve multiple accesses, as well as iteration, are never
5302+
atomic:
5303+
5304+
.. code-block::
5305+
:class: bad
5306+
5307+
# NOT atomic: check-then-act
5308+
if elem in s:
5309+
s.remove(elem)
5310+
5311+
# NOT thread-safe: iteration while modifying
5312+
for elem in s:
5313+
process(elem) # another thread may modify s
5314+
5315+
Consider external synchronization when sharing :class:`set` instances
5316+
across threads. See :ref:`freethreading-python-howto` for more information.
5317+
52125318

52135319
.. _typesmapping:
52145320

0 commit comments

Comments
 (0)