https://github.com/python/cpython/commit/79b91e7c50d30dce6599a15cc4459667e25d525e
commit: 79b91e7c50d30dce6599a15cc4459667e25d525e
branch: main
author: Lysandros Nikolaou <[email protected]>
committer: lysnikolaou <[email protected]>
date: 2026-03-13T14:53:01+01:00
summary:

gh-142518: Document thread-safety guarantees of set objects (#145225)

files:
M Doc/library/stdtypes.rst
M Doc/library/threadsafety.rst

diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index 6b55daa9b6eae0..7ae399eb95ba6e 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -5251,6 +5251,11 @@ Note, the *elem* argument to the 
:meth:`~object.__contains__`,
 :meth:`~set.discard` methods may be a set.  To support searching for an 
equivalent
 frozenset, a temporary one is created from *elem*.
 
+.. seealso::
+
+   For detailed information on thread-safety guarantees for :class:`set`
+   objects, see :ref:`thread-safety-set`.
+
 
 .. _typesmapping:
 
diff --git a/Doc/library/threadsafety.rst b/Doc/library/threadsafety.rst
index 7ab5921c7ec298..4f2eda19b85e07 100644
--- a/Doc/library/threadsafety.rst
+++ b/Doc/library/threadsafety.rst
@@ -342,3 +342,108 @@ thread, iterate over a copy:
 
 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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to