Skip to content
74 changes: 74 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,80 @@ 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

Most individual operations on :class:`list` instances are atomic:

.. code-block::
:class: good

# The following operations are atomic
lst1 + lst2 # atomic concatenation of two lists
x * lst # atomic repeat of lst x times
item = lst[i] # atomically retrieves item at index i
lst[i] = value # atomically replaces item at index i
lst *= x # atomically extend the list x times

# Calls to the following list methods are atomic
lst.clear()
lst.copy()
lst.append(item)
lst.insert(idx, item)
lst.pop(idx)
lst.remove(item)
lst.reverse()
lst.sort()

The following operations/methods are not fully atomic:

.. code-block::
:class: maybe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could say something like “Operations/methods that involve iteration are generally not atomic, except when used with specific built-in types”, and iteration itself can be moved here?
Mentioning iteration might help people make sense of this, i.e. it's no longer two arbitrary lists of operations/methods.

Then the bad section below would be left only with examples of “manually” combining multiple operations.

Copy link
Member Author

@lysnikolaou lysnikolaou Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how I feel about this. "Operations/methods that involve iteration are generally not atomic" is probably not a mnemonic we want people to use, because there are methods that are atomic but traverse the list. Granted most of those are ones that also mutate it, but e.g. list.copy doesn't.

But the idea of separating iteration from manually combining multiple operations is good. Maybe we should do just that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.
(I guess it's “operations that involve arbitrary iterators and/or comparison functions”, but that's too long; readers whom that would help can figure it out from the list.)

As for iteration, it sounds like the guarantees are the same for single-threaded code: iteration of a list that is being modified may skip elements or yield repeated elements, but will not crash or produce elements that were never part of the list. Is that right?


Is this the place to document list iterators -- i.e. what happens if you use a shared iterator in several threads?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completely reworded the docs to account for lock-free operations. Is this better?


I think documenting iterators should be done in the Iterator docs, not really individually for each iterator type.


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

lst.extend(iterable)
lst += iterable

lst[i:j] = iterable

The :meth:`~list.index` and :meth:`~list.count` methods, and the ``in``
operator, iterate the list without holding a lock. They are safe to call
concurrently but may return results affected by concurrent modifications.

:meth:`~list.extend` is safe to call from multiple threads. However, the
operation is fully atomic only when the iterable that's passed to ``extend``
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). Otherwise, an iterator is created which can be
concurrently modified by another thread. The same applies to inplace
concatenation of 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 not
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. See :ref:`freethreading-python-howto` for more information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting long enough that maybe it deserves to be in its own page in the reference docs, along with thread-safety notes for all the builtin types. Then we can link to those reference docs from here.

I think our hope when we originally wanted to include these notes directly in the docs for the builtins was that these notes would be pretty short. But it turns out there are a decent number of caveats and that hope might not be realistic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. Splitting everything out in a new page in the reference docs and then cross-linking sounds like the better approach, especially since dict docs are going to be even longer. @encukou What do you think?



.. _typesseq-tuple:

Expand Down
Loading