mirror of
https://github.com/python/cpython.git
synced 2026-05-09 12:00:49 +00:00
[3.14] gh-142518: add thread safety docs for dict and set APIs (#148392)
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
parent
620fb74384
commit
6112e2dd44
3 changed files with 211 additions and 2 deletions
|
|
@ -65,6 +65,11 @@ Dictionary Objects
|
|||
*key*, return ``1``, otherwise return ``0``. On error, return ``-1``.
|
||||
This is equivalent to the Python expression ``key in p``.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_ContainsString(PyObject *p, const char *key)
|
||||
|
||||
|
|
@ -87,6 +92,11 @@ Dictionary Objects
|
|||
``0`` on success or ``-1`` on failure. This function *does not* steal a
|
||||
reference to *val*.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_SetItemString(PyObject *p, const char *key, PyObject *val)
|
||||
|
||||
|
|
@ -102,6 +112,11 @@ Dictionary Objects
|
|||
If *key* is not in the dictionary, :exc:`KeyError` is raised.
|
||||
Return ``0`` on success or ``-1`` on failure.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_DelItemString(PyObject *p, const char *key)
|
||||
|
||||
|
|
@ -120,6 +135,11 @@ Dictionary Objects
|
|||
* If the key is missing, set *\*result* to ``NULL`` and return ``0``.
|
||||
* On error, raise an exception and return ``-1``.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
See also the :c:func:`PyObject_GetItem` function.
|
||||
|
|
@ -137,6 +157,13 @@ Dictionary Objects
|
|||
:meth:`~object.__eq__` methods are silently ignored.
|
||||
Prefer the :c:func:`PyDict_GetItemWithError` function instead.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, the returned
|
||||
:term:`borrowed reference` may become invalid if another thread modifies
|
||||
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
|
||||
returns a :term:`strong reference`.
|
||||
|
||||
.. versionchanged:: 3.10
|
||||
Calling this API without an :term:`attached thread state` had been allowed for historical
|
||||
reason. It is no longer allowed.
|
||||
|
|
@ -149,6 +176,13 @@ Dictionary Objects
|
|||
occurred. Return ``NULL`` **without** an exception set if the key
|
||||
wasn't present.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, the returned
|
||||
:term:`borrowed reference` may become invalid if another thread modifies
|
||||
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
|
||||
returns a :term:`strong reference`.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyDict_GetItemString(PyObject *p, const char *key)
|
||||
|
||||
|
|
@ -164,6 +198,13 @@ Dictionary Objects
|
|||
Prefer using the :c:func:`PyDict_GetItemWithError` function with your own
|
||||
:c:func:`PyUnicode_FromString` *key* instead.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, the returned
|
||||
:term:`borrowed reference` may become invalid if another thread modifies
|
||||
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemStringRef`,
|
||||
which returns a :term:`strong reference`.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
|
||||
|
||||
|
|
@ -184,6 +225,14 @@ Dictionary Objects
|
|||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, the returned
|
||||
:term:`borrowed reference` may become invalid if another thread modifies
|
||||
the dictionary concurrently. Prefer :c:func:`PyDict_SetDefaultRef`,
|
||||
which returns a :term:`strong reference`.
|
||||
|
||||
|
||||
|
||||
.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result)
|
||||
|
||||
|
|
@ -203,6 +252,11 @@ Dictionary Objects
|
|||
These may refer to the same object: in that case you hold two separate
|
||||
references to it.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
|
|
@ -220,6 +274,11 @@ Dictionary Objects
|
|||
Similar to :meth:`dict.pop`, but without the default value and
|
||||
not raising :exc:`KeyError` if the key is missing.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
|
|
@ -336,6 +395,13 @@ Dictionary Objects
|
|||
only be added if there is not a matching key in *a*. Return ``0`` on
|
||||
success or ``-1`` if an exception was raised.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, when *b* is a
|
||||
:class:`dict` (with the standard iterator), both *a* and *b* are locked
|
||||
for the duration of the operation. When *b* is a non-dict mapping, only
|
||||
*a* is locked; *b* may be concurrently modified by another thread.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_Update(PyObject *a, PyObject *b)
|
||||
|
||||
|
|
@ -345,6 +411,13 @@ Dictionary Objects
|
|||
argument has no "keys" attribute. Return ``0`` on success or ``-1`` if an
|
||||
exception was raised.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, when *b* is a
|
||||
:class:`dict` (with the standard iterator), both *a* and *b* are locked
|
||||
for the duration of the operation. When *b* is a non-dict mapping, only
|
||||
*a* is locked; *b* may be concurrently modified by another thread.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_MergeFromSeq2(PyObject *a, PyObject *seq2, int override)
|
||||
|
||||
|
|
@ -360,6 +433,13 @@ Dictionary Objects
|
|||
if override or key not in a:
|
||||
a[key] = value
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded <free threading>` build, only *a* is locked.
|
||||
The iteration over *seq2* is not synchronized; *seq2* may be concurrently
|
||||
modified by another thread.
|
||||
|
||||
|
||||
.. c:function:: int PyDict_AddWatcher(PyDict_WatchCallback callback)
|
||||
|
||||
Register *callback* as a dictionary watcher. Return a non-negative integer
|
||||
|
|
@ -367,6 +447,13 @@ Dictionary Objects
|
|||
of error (e.g. no more watcher IDs available), return ``-1`` and set an
|
||||
exception.
|
||||
|
||||
.. note::
|
||||
|
||||
This function is not internally synchronized. In the
|
||||
:term:`free-threaded <free threading>` build, callers should ensure no
|
||||
concurrent calls to :c:func:`PyDict_AddWatcher` or
|
||||
:c:func:`PyDict_ClearWatcher` are in progress.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. c:function:: int PyDict_ClearWatcher(int watcher_id)
|
||||
|
|
@ -375,6 +462,13 @@ Dictionary Objects
|
|||
:c:func:`PyDict_AddWatcher`. Return ``0`` on success, ``-1`` on error (e.g.
|
||||
if the given *watcher_id* was never registered.)
|
||||
|
||||
.. note::
|
||||
|
||||
This function is not internally synchronized. In the
|
||||
:term:`free-threaded <free threading>` build, callers should ensure no
|
||||
concurrent calls to :c:func:`PyDict_AddWatcher` or
|
||||
:c:func:`PyDict_ClearWatcher` are in progress.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. c:function:: int PyDict_Watch(int watcher_id, PyObject *dict)
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ the constructor functions work with any iterable Python object.
|
|||
actually iterable. The constructor is also useful for copying a set
|
||||
(``c=set(s)``).
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyFrozenSet_New(PyObject *iterable)
|
||||
|
||||
|
|
@ -100,6 +105,11 @@ the constructor functions work with any iterable Python object.
|
|||
set on success or ``NULL`` on failure. Raise :exc:`TypeError` if *iterable* is
|
||||
not actually iterable.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`.
|
||||
|
||||
|
||||
The following functions and macros are available for instances of :class:`set`
|
||||
or :class:`frozenset` or instances of their subtypes.
|
||||
|
|
@ -127,6 +137,10 @@ or :class:`frozenset` or instances of their subtypes.
|
|||
the *key* is unhashable. Raise :exc:`SystemError` if *anyset* is not a
|
||||
:class:`set`, :class:`frozenset`, or an instance of a subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
.. c:function:: int PySet_Add(PyObject *set, PyObject *key)
|
||||
|
||||
|
|
@ -138,6 +152,12 @@ or :class:`frozenset` or instances of their subtypes.
|
|||
:exc:`SystemError` if *set* is not an instance of :class:`set` or its
|
||||
subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
|
||||
|
||||
The following functions are available for instances of :class:`set` or its
|
||||
subtypes but not for instances of :class:`frozenset` or its subtypes.
|
||||
|
|
@ -152,6 +172,11 @@ subtypes but not for instances of :class:`frozenset` or its subtypes.
|
|||
temporary frozensets. Raise :exc:`SystemError` if *set* is not an
|
||||
instance of :class:`set` or its subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
The operation is atomic on :term:`free threading <free-threaded build>`
|
||||
when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PySet_Pop(PyObject *set)
|
||||
|
||||
|
|
@ -167,6 +192,12 @@ subtypes but not for instances of :class:`frozenset` or its subtypes.
|
|||
success. Return ``-1`` and raise :exc:`SystemError` if *set* is not an instance of
|
||||
:class:`set` or its subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
In the :term:`free-threaded build`, the set is emptied before its entries
|
||||
are cleared, so other threads will observe an empty set rather than
|
||||
intermediate states.
|
||||
|
||||
|
||||
Deprecated API
|
||||
^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -14,10 +14,71 @@
|
|||
# The function name must match the C domain identifier used in the documentation.
|
||||
|
||||
# Synchronization primitives (Doc/c-api/synchronization.rst)
|
||||
PyMutex_Lock:shared:
|
||||
PyMutex_Unlock:shared:
|
||||
PyMutex_Lock:atomic:
|
||||
PyMutex_Unlock:atomic:
|
||||
PyMutex_IsLocked:atomic:
|
||||
|
||||
|
||||
# Dictionary objects (Doc/c-api/dict.rst)
|
||||
|
||||
# Type checks - read ob_type pointer, always safe
|
||||
PyDict_Check:atomic:
|
||||
PyDict_CheckExact:atomic:
|
||||
|
||||
# Creation - pure allocation, no shared state
|
||||
PyDict_New:atomic:
|
||||
|
||||
# Lock-free lookups - use _Py_dict_lookup_threadsafe(), no locking.
|
||||
# Atomic with simple types.
|
||||
PyDict_Contains:shared:
|
||||
PyDict_ContainsString:atomic:
|
||||
PyDict_GetItemRef:shared:
|
||||
PyDict_GetItemStringRef:atomic:
|
||||
PyDict_Size:atomic:
|
||||
PyDict_GET_SIZE:atomic:
|
||||
|
||||
# Borrowed-reference lookups - lock-free dict access but returned
|
||||
# borrowed reference is unsafe in free-threaded builds without
|
||||
# external synchronization
|
||||
PyDict_GetItem:compatible:
|
||||
PyDict_GetItemWithError:compatible:
|
||||
PyDict_GetItemString:compatible:
|
||||
PyDict_SetDefault:compatible:
|
||||
|
||||
# Iteration - no locking; returns borrowed refs
|
||||
PyDict_Next:compatible:
|
||||
|
||||
# Single-item mutations - protected by per-object critical section
|
||||
PyDict_SetItem:shared:
|
||||
PyDict_SetItemString:atomic:
|
||||
PyDict_DelItem:shared:
|
||||
PyDict_DelItemString:atomic:
|
||||
PyDict_SetDefaultRef:shared:
|
||||
PyDict_Pop:shared:
|
||||
PyDict_PopString:atomic:
|
||||
|
||||
# Bulk reads - hold per-object lock for duration
|
||||
PyDict_Clear:atomic:
|
||||
PyDict_Copy:atomic:
|
||||
PyDict_Keys:atomic:
|
||||
PyDict_Values:atomic:
|
||||
PyDict_Items:atomic:
|
||||
|
||||
# Merge/update - lock target dict; also lock source when it is a dict
|
||||
PyDict_Update:shared:
|
||||
PyDict_Merge:shared:
|
||||
PyDict_MergeFromSeq2:shared:
|
||||
|
||||
# Watcher registration - no synchronization on interpreter state
|
||||
PyDict_AddWatcher:compatible:
|
||||
PyDict_ClearWatcher:compatible:
|
||||
|
||||
# Per-dict watcher tags - non-atomic RMW on _ma_watcher_tag;
|
||||
# safe on distinct dicts only
|
||||
PyDict_Watch:distinct:
|
||||
PyDict_Unwatch:distinct:
|
||||
|
||||
|
||||
# List objects (Doc/c-api/list.rst)
|
||||
|
||||
# Type checks - read ob_type pointer, always safe
|
||||
|
|
@ -125,6 +186,29 @@ PyByteArray_GET_SIZE:atomic:
|
|||
PyByteArray_AsString:compatible:
|
||||
PyByteArray_AS_STRING:compatible:
|
||||
|
||||
# Creation - may iterate the iterable argument, calling arbitrary code.
|
||||
# Atomic for sets, frozensets, dicts, and frozendicts.
|
||||
PySet_New:shared:
|
||||
PyFrozenSet_New:shared:
|
||||
|
||||
# Size - uses atomic load on free-threaded builds
|
||||
PySet_Size:atomic:
|
||||
PySet_GET_SIZE:atomic:
|
||||
|
||||
# Contains - lock-free, atomic with simple types
|
||||
PySet_Contains:shared:
|
||||
|
||||
# Mutations - hold per-object lock for duration
|
||||
# atomic with simple types
|
||||
PySet_Add:shared:
|
||||
PySet_Discard:shared:
|
||||
|
||||
# Pop - hold per-object lock for duration
|
||||
PySet_Pop:atomic:
|
||||
|
||||
# Clear - empties the set before clearing
|
||||
PySet_Clear:atomic:
|
||||
|
||||
# Capsule objects (Doc/c-api/capsule.rst)
|
||||
|
||||
# Type check - read ob_type pointer, always safe
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue