Skip to content

Commit d817556

Browse files
committed
Add dict thread safety docs
1 parent 052e55e commit d817556

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

Doc/library/stdtypes.rst

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5508,6 +5508,115 @@ can be used interchangeably to index the same dictionary entry.
55085508
.. versionchanged:: 3.8
55095509
Dictionaries are now reversible.
55105510

5511+
.. admonition:: Thread safety
5512+
5513+
The following operations and function are lock-free and
5514+
:term:`atomic <atomic operation>`.
5515+
5516+
.. code-block::
5517+
:class: good
5518+
5519+
d[key] # dict.__getitem__
5520+
d.get(key) # dict.get
5521+
key in d # dict.__contains__
5522+
len(d) # dict.__len__
5523+
5524+
These operations may compare keys using :meth:`~object.__eq__`, which can
5525+
execute arbitrary Python code. During such comparisons, the dictionary may
5526+
be modified by another thread. For built-in types like :class:`str`,
5527+
:class:`int`, and :class:`float`, that implement :meth:`~object.__eq__` in C,
5528+
the underlying lock is not released during comparisons and this is not a
5529+
concern.
5530+
5531+
All other operations from here on hold the per-object lock.
5532+
5533+
Writing or removing a single item is safe to call from multiple threads
5534+
and will not corrupt the dictionary:
5535+
5536+
.. code-block::
5537+
:class: good
5538+
5539+
d[key] = value # write
5540+
del d[key] # delete
5541+
d.pop(key) # remove and return
5542+
d.popitem() # remove and return last item
5543+
d.setdefault(key, v) # insert if missing
5544+
5545+
These operations also compare keys, so the same :meth:`~object.__eq__`
5546+
considerations as above apply.
5547+
5548+
The following operations return new objects and hold the per-object lock
5549+
for the duration:
5550+
5551+
.. code-block::
5552+
:class: good
5553+
5554+
d.copy() # returns a shallow copy of the dictionary
5555+
d | other # merges two dicts into a new dict
5556+
d.keys() # returns a new dict_keys view object
5557+
d.values() # returns a new dict_values view object
5558+
d.items() # returns a new dict_items view object
5559+
5560+
The :meth:`~dict.clear` method holds the lock for its duration. Other
5561+
threads cannot observe elements being removed.
5562+
5563+
The following operations lock both dictionaries. For :meth:`~dict.update`
5564+
and ``|=``, this applies only when the other operand is a :class:`dict`
5565+
that uses the standard dict iterator (but not subclasses that override
5566+
iteration). For equality comparison, this applies to :class:`dict` and
5567+
its subclasses:
5568+
5569+
.. code-block::
5570+
:class: good
5571+
5572+
d.update(other_dict) # both locked when other_dict is a dict
5573+
d |= other_dict # both locked when other_dict is a dict
5574+
d == other_dict # both locked for dict and subclasses
5575+
5576+
The equality comparison also compares values using :meth:`~object.__eq__`,
5577+
so for non-built-in types the lock may be released during comparison.
5578+
5579+
:meth:`~dict.fromkeys` locks both the new dictionary and the iterable
5580+
when the iterable is exactly a :class:`dict`, :class:`set`, or
5581+
:class:`frozenset` (not subclasses):
5582+
5583+
.. code-block::
5584+
:class: good
5585+
5586+
dict.fromkeys(a_dict) # locks both
5587+
dict.fromkeys(a_set) # locks both
5588+
dict.fromkeys(a_frozenset) # locks both
5589+
5590+
When updating from a non-dict iterable, only the target dictionary is
5591+
locked. The iterable may be concurrently modified by another thread:
5592+
5593+
.. code-block::
5594+
:class: maybe
5595+
5596+
d.update(iterable) # iterable is not a dict
5597+
d |= iterable # iterable is not a dict
5598+
dict.fromkeys(iterable) # iterable is not a dict/set/frozenset
5599+
5600+
Operations that involve multiple accesses, as well as iteration, are never
5601+
atomic:
5602+
5603+
.. code-block::
5604+
:class: bad
5605+
5606+
# NOT atomic: read-modify-write
5607+
d[key] = d[key] + 1
5608+
5609+
# NOT atomic: check-then-act
5610+
if key in d:
5611+
del d[key]
5612+
5613+
# NOT thread-safe: iteration while modifying
5614+
for key in d:
5615+
process(key) # another thread may modify d
5616+
5617+
Consider external synchronization when sharing :class:`dict` instances
5618+
across threads. See :ref:`freethreading-python-howto` for more information.
5619+
55115620

55125621
.. seealso::
55135622
:class:`types.MappingProxyType` can be used to create a read-only view

0 commit comments

Comments
 (0)