Skip to content

Commit 3ba0282

Browse files
committed
Change levels and add new section to thread safety page
- Change levels to include atomic and safe on distinct objects. - Add a section explaining the levels in the thread safety page - Cross reference levels section from annotation
1 parent d577860 commit 3ba0282

File tree

4 files changed

+111
-28
lines changed

4 files changed

+111
-28
lines changed

Doc/data/threadsafety.dat

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
# Where level is one of:
77
# incompatible -- not safe even with external locking
88
# compatible -- safe if the caller serializes all access with external locks
9-
# safe -- safe for concurrent use
9+
# distinct -- safe on distinct objects without external synchronization
10+
# shared -- safe for concurrent use on the same object
11+
# atomic -- atomic
1012
#
1113
# Lines beginning with '#' are ignored.
1214
# The function name must match the C domain identifier used in the documentation.
1315

1416
# Synchronization primitives (Doc/c-api/synchronization.rst)
15-
PyMutex_Lock:safe:
16-
PyMutex_Unlock:safe:
17-
PyMutex_IsLocked:safe:
17+
PyMutex_Lock:shared:
18+
PyMutex_Unlock:shared:
19+
PyMutex_IsLocked:atomic:

Doc/glossary.rst

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,19 +1587,6 @@ Glossary
15871587
See :ref:`Thread State and the Global Interpreter Lock <threads>` for more
15881588
information.
15891589

1590-
thread-compatible
1591-
A function or operation that is safe to call from multiple threads
1592-
provided the caller supplies appropriate external synchronization, for
1593-
example by holding a :term:`lock` for the duration of each call. Without
1594-
such synchronization, concurrent calls may produce :term:`race conditions
1595-
<race condition>` or :term:`data races <data race>`.
1596-
1597-
thread-incompatible
1598-
A function or operation that cannot be made safe for concurrent use even
1599-
with external synchronization. Thread-incompatible code typically
1600-
accesses global state in an unsynchronized way and must be called from
1601-
only one thread at a time throughout the program's lifetime.
1602-
16031590
thread-safe
16041591
A module, function, or class that behaves correctly when used by multiple
16051592
threads concurrently. Thread-safe code uses appropriate

Doc/library/threadsafety.rst

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,88 @@ For general guidance on writing thread-safe code in free-threaded Python, see
1313
:ref:`freethreading-python-howto`.
1414

1515

16+
.. _threadsafety-levels:
17+
18+
Thread safety levels
19+
====================
20+
21+
The C API documentation uses the following levels to describe the thread
22+
safety guarantees of each function. The levels are listed from least to
23+
most safe.
24+
25+
.. _threadsafety-level-incompatible:
26+
27+
Incompatible
28+
------------
29+
30+
A function or operation that cannot be made safe for concurrent use even
31+
with external synchronization. Incompatible code typically accesses
32+
global state in an unsynchronized way and must only be called from a single
33+
thread throughout the program's lifetime.
34+
35+
Example: a function that modifies process-wide state such as signal handlers
36+
or environment variables, where concurrent calls from any threads, even with
37+
external locking, can conflict with the runtime or other libraries.
38+
39+
.. _threadsafety-level-compatible:
40+
41+
Compatible
42+
----------
43+
44+
A function or operation that is safe to call from multiple threads
45+
*provided* the caller supplies appropriate external synchronization, for
46+
example by holding a :term:`lock` for the duration of each call. Without
47+
such synchronization, concurrent calls may produce :term:`race conditions
48+
<race condition>` or :term:`data races <data race>`.
49+
50+
Example: a function that reads from or writes to an object whose internal
51+
state is not protected by a lock. Callers must ensure that no two threads
52+
access the same object at the same time.
53+
54+
.. _threadsafety-level-distinct:
55+
56+
Safe on distinct objects
57+
------------------------
58+
59+
A function or operation that is safe to call from multiple threads without
60+
external synchronization, as long as each thread operates on a **different**
61+
object. Two threads may call the function at the same time, but they must
62+
not pass the same object (or objects that share underlying state) as
63+
arguments.
64+
65+
Example: a function that modifies fields of a struct using non-atomic
66+
writes. Two threads can each call the function on their own struct
67+
instance safely, but concurrent calls on the *same* instance require
68+
external synchronization.
69+
70+
.. _threadsafety-level-shared:
71+
72+
Safe on shared objects
73+
----------------------
74+
75+
A function or operation that is safe for concurrent use on the **same**
76+
object. The implementation uses internal synchronization (such as
77+
:term:`per-object locks <per-object lock>` or
78+
:ref:`critical sections <python-critical-section-api>`) to protect shared
79+
mutable state, so callers do not need to supply their own locking.
80+
81+
Example: :c:func:`PyMutex_Lock` can be called from multiple threads on the
82+
same :c:type:`PyMutex` - it uses internal synchronization to serialize
83+
access.
84+
85+
.. _threadsafety-level-atomic:
86+
87+
Atomic
88+
------
89+
90+
A function or operation that appears :term:`atomic <atomic operation>` with
91+
respect to other threads - it executes instantaneously from the perspective
92+
of other threads. This is the strongest form of thread safety.
93+
94+
Example: :c:func:`PyMutex_IsLocked` performs an atomic read of the mutex
95+
state and can be called from any thread at any time.
96+
97+
1698
.. _thread-safety-list:
1799

18100
Thread safety for list objects

Doc/tools/extensions/c_annotations.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ def read_stable_abi_data(stable_abi_file: Path) -> dict[str, StableABIEntry]:
127127
_VALID_THREADSAFETY_LEVELS = frozenset({
128128
"incompatible",
129129
"compatible",
130-
"safe",
130+
"distinct",
131+
"shared",
132+
"atomic",
131133
})
132134

133135

@@ -307,25 +309,35 @@ def _threadsafety_annotation(level: str) -> nodes.emphasis:
307309
match level:
308310
case "incompatible":
309311
display = sphinx_gettext("Not safe to call from multiple threads.")
310-
reftarget = "thread-incompatible"
312+
reftarget = "threadsafety-level-incompatible"
311313
case "compatible":
312314
display = sphinx_gettext(
313-
"Safe to call from multiple threads with external synchronization only."
315+
"Safe to call from multiple threads"
316+
" with external synchronization only."
314317
)
315-
reftarget = "thread-compatible"
316-
case "safe":
317-
display = sphinx_gettext("Safe for concurrent use.")
318-
reftarget = "thread-safe"
319-
case _:
320-
raise AssertionError(
321-
"Only the levels 'incompatible', 'compatible' and 'safe' are possible"
318+
reftarget = "threadsafety-level-compatible"
319+
case "distinct":
320+
display = sphinx_gettext(
321+
"Safe to call without external synchronization"
322+
" on distinct objects."
323+
)
324+
reftarget = "threadsafety-level-distinct"
325+
case "shared":
326+
display = sphinx_gettext(
327+
"Safe for concurrent use on the same object."
322328
)
329+
reftarget = "threadsafety-level-shared"
330+
case "atomic":
331+
display = sphinx_gettext("Atomic.")
332+
reftarget = "threadsafety-level-atomic"
333+
case _:
334+
raise AssertionError(f"Unknown thread safety level {level!r}")
323335
ref_node = addnodes.pending_xref(
324336
display,
325337
nodes.Text(display),
326338
refdomain="std",
327339
reftarget=reftarget,
328-
reftype="term",
340+
reftype="ref",
329341
refexplicit="True",
330342
)
331343
prefix = sphinx_gettext("Thread safety:") + " "

0 commit comments

Comments
 (0)