Doc/library/threadsafety.rst
.. _threadsafety:
Thread Safety Guarantees
This page documents thread-safety guarantees for built-in types in Python's
free-threaded build. The guarantees described here apply when using Python with
the :term:GIL disabled (free-threaded mode). When the GIL is enabled, most
operations are implicitly serialized.
For general guidance on writing thread-safe code in free-threaded Python, see
:ref:freethreading-python-howto.
.. _threadsafety-levels:
The C API documentation uses the following levels to describe the thread safety guarantees of each function. The levels are listed from least to most safe.
.. _threadsafety-level-incompatible:
A function or operation that cannot be made safe for concurrent use even with external synchronization. Incompatible code typically accesses global state in an unsynchronized way and must only be called from a single thread throughout the program's lifetime.
Example: a function that modifies process-wide state such as signal handlers or environment variables, where concurrent calls from any threads, even with external locking, can conflict with the runtime or other libraries.
.. _threadsafety-level-compatible:
A function or operation that is safe to call from multiple threads
provided the caller supplies appropriate external synchronization, for
example by holding a :term:lock for the duration of each call. Without
such synchronization, concurrent calls may produce :term:race conditions <race condition> or :term:data races <data race>.
Example: a function that reads from or writes to an object whose internal state is not protected by a lock. Callers must ensure that no two threads access the same object at the same time.
.. _threadsafety-level-distinct:
A function or operation that is safe to call from multiple threads without external synchronization, as long as each thread operates on a different object. Two threads may call the function at the same time, but they must not pass the same object (or objects that share underlying state) as arguments.
Example: a function that modifies fields of a struct using non-atomic writes. Two threads can each call the function on their own struct instance safely, but concurrent calls on the same instance require external synchronization.
.. _threadsafety-level-shared:
A function or operation that is safe for concurrent use on the same
object. The implementation uses internal synchronization (such as
:term:per-object locks <per-object lock> or
:ref:critical sections <python-critical-section-api>) to protect shared
mutable state, so callers do not need to supply their own locking.
Example: :c:func:PyList_GetItemRef can be called from multiple threads on the
same :c:type:PyListObject - it uses internal synchronization to serialize
access.
.. _threadsafety-level-atomic:
A function or operation that appears :term:atomic <atomic operation> with
respect to other threads - it executes instantaneously from the perspective
of other threads. This is the strongest form of thread safety.
Example: :c:func:PyMutex_IsLocked performs an atomic read of the mutex
state and can be called from any thread at any time.
.. _thread-safety-list:
Reading a single element from a :class:list is
:term:atomic <atomic operation>:
.. code-block:: :class: good
lst[i] # list.getitem
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:
.. code-block:: :class: maybe
item in lst lst.index(item) lst.count(item)
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.
All other operations from here on block using the :term:per-object lock.
Writing a single item via lst[i] = x is safe to call from multiple
threads and will not corrupt the list.
The following operations return new objects and appear
:term:atomic <atomic operation> to other threads:
.. code-block:: :class: good
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
The following methods that only operate on a single element with no shifting
required are :term:atomic <atomic operation>:
.. code-block:: :class: good
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.clear method is also :term:atomic <atomic operation>.
Other threads cannot observe elements being removed.
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 following operations may allow :term:lock-free operations to observe
intermediate states since they modify multiple elements in place:
.. code-block:: :class: maybe
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
The :meth:~list.remove method may allow concurrent modifications since
element comparison may execute arbitrary Python code (via
:meth:~object.__eq__).
: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.
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 never atomic. For example:
.. code-block:: :class: bad
lst[i] = lst[i] + 1
if lst: item = lst.pop()
for item in lst: process(item) # another thread may modify lst
Consider external synchronization when sharing :class:list instances
across threads.
.. _thread-safety-dict:
Creating a dictionary with the :class:dict constructor is atomic when the
argument to it is a :class:dict or a :class:tuple. When using the
:meth:dict.fromkeys method, dictionary creation is atomic when the
argument is a :class:dict, :class:tuple, :class:set or
:class:frozenset.
The following operations and functions are :term:lock-free and
:term:atomic <atomic operation>.
.. code-block:: :class: good
d[key] # dict.getitem d.get(key) # dict.get key in d # dict.contains len(d) # dict.len
All other operations from here on hold the :term:per-object lock.
Writing or removing a single item is safe to call from multiple threads and will not corrupt the dictionary:
.. code-block:: :class: good
d[key] = value # write del d[key] # delete d.pop(key) # remove and return d.popitem() # remove and return last item d.setdefault(key, v) # insert if missing
These operations may compare keys using :meth:~object.__eq__, which can
execute arbitrary Python code. During such comparisons, the dictionary may
be modified by another thread. For built-in types like :class:str,
:class:int, and :class:float, that implement :meth:~object.__eq__ in C,
the underlying lock is not released during comparisons and this is not a
concern.
The following operations return new objects and hold the :term:per-object lock
for the duration of the operation:
.. code-block:: :class: good
d.copy() # returns a shallow copy of the dictionary d | other # merges two dicts into a new dict d.keys() # returns a new dict_keys view object d.values() # returns a new dict_values view object d.items() # returns a new dict_items view object
The :meth:~dict.clear method holds the lock for its duration. Other
threads cannot observe elements being removed.
The following operations lock both dictionaries. For :meth:~dict.update
and |=, this applies only when the other operand is a :class:dict
that uses the standard dict iterator (but not subclasses that override
iteration). For equality comparison, this applies to :class:dict and
its subclasses:
.. code-block:: :class: good
d.update(other_dict) # both locked when other_dict is a dict d |= other_dict # both locked when other_dict is a dict d == other_dict # both locked for dict and subclasses
All comparison operations also compare values using :meth:~object.__eq__,
so for non-built-in types the lock may be released during comparison.
:meth:~dict.fromkeys locks both the new dictionary and the iterable
when the iterable is exactly a :class:dict, :class:set, or
:class:frozenset (not subclasses):
.. code-block:: :class: good
dict.fromkeys(a_dict) # locks both dict.fromkeys(a_set) # locks both dict.fromkeys(a_frozenset) # locks both
When updating from a non-dict iterable, only the target dictionary is locked. The iterable may be concurrently modified by another thread:
.. code-block:: :class: maybe
d.update(iterable) # iterable is not a dict: only d locked d |= iterable # iterable is not a dict: only d locked dict.fromkeys(iterable) # iterable is not a dict/set/frozenset: only result locked
Operations that involve multiple accesses, as well as iteration, are never atomic:
.. code-block:: :class: bad
d[key] = d[key] + 1
if key in d: del d[key]
for key, value in d.items(): process(key) # another thread may modify d
To avoid time-of-check to time-of-use (TOCTOU) issues, use atomic operations or handle exceptions:
.. code-block:: :class: good
d.pop(key, None)
try: del d[key] except KeyError: pass
To safely iterate over a dictionary that may be modified by another thread, iterate over a copy:
.. code-block:: :class: good
for key, value in d.copy().items(): process(key)
Consider external synchronization when sharing :class:dict instances
across threads.
.. _thread-safety-set:
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:
set.update and :meth:set.union lock both objects only when
the other operand is a :class:set, :class:frozenset, or :class:dict.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:
set.difference_update and :meth:set.intersection_update try
to lock all objects one-by-one.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
if elem in s: s.remove(elem)
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.
.. _thread-safety-bytearray:
The :func:len function is lock-free and :term:atomic <atomic operation>.
Concatenation and comparisons use the buffer protocol, which prevents resizing but does not hold the per-object lock. These operations may observe intermediate states from concurrent modifications:
.. code-block:: :class: maybe
ba + other # may observe concurrent writes
ba == other # may observe concurrent writes
ba < other # may observe concurrent writes
All other operations from here on hold the per-object lock.
Reading a single element or slice is safe to call from multiple threads:
.. code-block:: :class: good
ba[i] # bytearray.__getitem__
ba[i:j] # slice
The following operations are safe to call from multiple threads and will not corrupt the bytearray:
.. code-block:: :class: good
ba[i] = x # write single byte
ba[i:j] = values # write slice
ba.append(x) # append single byte
ba.extend(other) # extend with iterable
ba.insert(i, x) # insert single byte
ba.pop() # remove and return last byte
ba.pop(i) # remove and return byte at index
ba.remove(x) # remove first occurrence
ba.reverse() # reverse in place
ba.clear() # remove all bytes
Slice assignment locks both objects when values is a :class:bytearray:
.. code-block:: :class: good
ba[i:j] = other_bytearray # both locked
The following operations return new objects and hold the per-object lock for the duration:
.. code-block:: :class: good
ba.copy() # returns a shallow copy
ba * n # repeat into new bytearray
The membership test holds the lock for its duration:
.. code-block:: :class: good
x in ba # bytearray.__contains__
All other bytearray methods (such as :meth:~bytearray.find,
:meth:~bytearray.replace, :meth:~bytearray.split,
:meth:~bytearray.decode, etc.) hold the per-object lock for their
duration.
Operations that involve multiple accesses, as well as iteration, are never atomic:
.. code-block:: :class: bad
# NOT atomic: check-then-act
if x in ba:
ba.remove(x)
# NOT thread-safe: iteration while modifying
for byte in ba:
process(byte) # another thread may modify ba
To safely iterate over a bytearray that may be modified by another thread, iterate over a copy:
.. code-block:: :class: good
# Make a copy to iterate safely
for byte in ba.copy():
process(byte)
Consider external synchronization when sharing :class:bytearray instances
across threads. See :ref:freethreading-python-howto for more information.
.. _thread-safety-memoryview:
:class:memoryview objects provide access to the internal data of an
underlying object without copying. Thread safety depends on both the
memoryview itself and the underlying buffer exporter.
The memoryview implementation uses atomic operations to track its own
exports in the :term:free-threaded build. Creating and
releasing a memoryview are thread-safe. Attribute access (e.g.,
:attr:~memoryview.shape, :attr:~memoryview.format) reads fields that
are immutable for the lifetime of the memoryview, so concurrent reads
are safe as long as the memoryview has not been released.
However, the actual data accessed through the memoryview is owned by the underlying object. Concurrent access to this data is only safe if the underlying object supports it:
For immutable objects like :class:bytes, concurrent reads through
multiple memoryviews are safe.
For mutable objects like :class:bytearray, reading and writing the
same memory region from multiple threads without external
synchronization is not safe and may result in data corruption.
Note that even read-only memoryviews of mutable objects do not
prevent data races if the underlying object is modified from
another thread.
.. code-block:: :class: bad
data = bytearray(1000) view = memoryview(data)
.. code-block:: :class: good
import threading lock = threading.Lock() data = bytearray(1000) view = memoryview(data)
with lock: view[0:500] = b'x' * 500
Resizing or reallocating the underlying object (such as calling
:meth:bytearray.resize) while a memoryview is exported raises
:exc:BufferError. This is enforced regardless of threading.