Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,16 @@ Glossary
to locks exist such as queues, producer/consumer patterns, and
thread-local state. See also :term:`deadlock`, and :term:`reentrant`.

lock-free
An operation that does not acquire any :term:`lock` and uses atomic CPU
instructions to ensure correctness. Lock-free operations can execute
concurrently without blocking each other and cannot be blocked by
operations that hold locks. In :term:`free-threaded <free threading>`
Python, built-in types like :class:`dict` and :class:`list` provide
lock-free read operations, which means other threads may observe
intermediate states during multi-step modifications even when those
modifications hold the :term:`per-object lock`.

loader
An object that loads a module.
It must define the :meth:`!exec_module` and :meth:`!create_module` methods
Expand Down Expand Up @@ -1217,6 +1227,16 @@ Glossary
<faq-argument-vs-parameter>`, the :class:`inspect.Parameter` class, the
:ref:`function` section, and :pep:`362`.

per-object lock
A :term:`lock` associated with an individual object instance rather than
a global lock shared across all objects. In :term:`free-threaded
<free threading>` Python, built-in types like :class:`dict` and
:class:`list` use per-object locks to allow concurrent operations on
different objects while serializing operations on the same object.
Operations that hold the per-object lock prevent other locking operations
on the same object from proceeding, but do not block :term:`lock-free`
operations.

path entry
A single location on the :term:`import path` which the :term:`path
based finder` consults to find modules for importing.
Expand Down
149 changes: 76 additions & 73 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1441,108 +1441,111 @@ application).
list appear empty for the duration, and raises :exc:`ValueError` if it can
detect that the list has been mutated during a sort.

.. admonition:: Thread safety
.. _thread-safety-list:

Reading a single element from a :class:`list` is
:term:`atomic <atomic operation>`:
.. rubric:: Thread safety for list objects

.. code-block::
:class: green
Reading a single element from a :class:`list` is
:term:`atomic <atomic operation>`:

lst[i] # list.__getitem__
.. code-block::
:class: green

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:
lst[i] # list.__getitem__

.. code-block::
:class: maybe
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:

item in lst
lst.index(item)
lst.count(item)
.. code-block::
:class: maybe

All of the above methods/operations are also lock-free. They do not block
concurrent modifications. Other operations that hold a lock will not block
these from observing intermediate states.
item in lst
lst.index(item)
lst.count(item)

All other operations from here on block using the per-object lock.
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.

Writing a single item via ``lst[i] = x`` is safe to call from multiple
threads and will not corrupt the list.
All other operations from here on block using the :term:`per-object lock`.

The following operations return new objects and appear
:term:`atomic <atomic operation>` to other threads:
Writing a single item via ``lst[i] = x`` is safe to call from multiple
threads and will not corrupt the list.

.. code-block::
:class: good
The following operations return new objects and appear
:term:`atomic <atomic operation>` to other threads:

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
.. code-block::
:class: good

Methods that only operate on a single elements with no shifting required are
:term:`atomic <atomic operation>`:
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

.. code-block::
:class: good
Methods that only operate on a single elements with no shifting required are
:term:`atomic <atomic operation>`:

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
.. code-block::
:class: good

The :meth:`~list.clear` method is also :term:`atomic <atomic operation>`.
Other threads cannot observe elements being removed.
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.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 :meth:`~list.clear` method is also :term:`atomic <atomic operation>`.
Other threads cannot observe elements being removed.

The following operations may allow lock-free operations to observe
intermediate states since they modify multiple elements in place:
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.

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

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
.. code-block::
:class: maybe

The :meth:`~list.remove` method may allow concurrent modifications since
element comparison may execute arbitrary Python code (via
:meth:`~object.__eq__`).
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

: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``.
The :meth:`~list.remove` method may allow concurrent modifications since
element comparison may execute arbitrary Python code (via
:meth:`~object.__eq__`).

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).
: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``.

Operations that involve multiple accesses, as well as iteration, are never
atomic. For example:
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).

.. code-block::
:class: bad
Operations that involve multiple accesses, as well as iteration, are never
atomic. For example:

# NOT atomic: read-modify-write
lst[i] = lst[i] + 1
.. code-block::
:class: bad

# NOT atomic: check-then-act
if lst:
item = lst.pop()
# NOT atomic: read-modify-write
lst[i] = lst[i] + 1

# NOT thread-safe: iteration while modifying
for item in lst:
process(item) # another thread may modify lst
# NOT atomic: check-then-act
if lst:
item = lst.pop()

Consider external synchronization when sharing :class:`list` instances
across threads. See :ref:`freethreading-python-howto` for more information.
# 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. See :ref:`freethreading-python-howto` for more information.


.. _typesseq-tuple:
Expand Down
Loading