| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 03fa2df commit 8f17d69
15 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2202,3 +2202,107 @@ The C-API provides a basic mutual exclusion lock. | |||
| 2202 | 2202 | issue a fatal error. | |
| 2203 | 2203 | ||
| 2204 | 2204 | .. versionadded:: 3.13 | |
| 2205 | + | ||
| 2206 | + .. _python-critical-section-api: | ||
| 2207 | + | ||
| 2208 | + Python Critical Section API | ||
| 2209 | + --------------------------- | ||
| 2210 | + | ||
| 2211 | + The critical section API provides a deadlock avoidance layer on top of | ||
| 2212 | + per-object locks for :term:`free-threaded <free threading>` CPython. They are | ||
| 2213 | + intended to replace reliance on the :term:`global interpreter lock`, and are | ||
| 2214 | + no-ops in versions of Python with the global interpreter lock. | ||
| 2215 | + | ||
| 2216 | + Critical sections avoid deadlocks by implicitly suspending active critical | ||
| 2217 | + sections and releasing the locks during calls to :c:func:`PyEval_SaveThread`. | ||
| 2218 | + When :c:func:`PyEval_RestoreThread` is called, the most recent critical section | ||
| 2219 | + is resumed, and its locks reacquired. This means the critical section API | ||
| 2220 | + provides weaker guarantees than traditional locks -- they are useful because | ||
| 2221 | + their behavior is similar to the :term:`GIL`. | ||
| 2222 | + | ||
| 2223 | + The functions and structs used by the macros are exposed for cases | ||
| 2224 | + where C macros are not available. They should only be used as in the | ||
| 2225 | + given macro expansions. Note that the sizes and contents of the structures may | ||
| 2226 | + change in future Python versions. | ||
| 2227 | + | ||
| 2228 | + .. note:: | ||
| 2229 | + | ||
| 2230 | + Operations that need to lock two objects at once must use | ||
| 2231 | + :c:macro:`Py_BEGIN_CRITICAL_SECTION2`. You *cannot* use nested critical | ||
| 2232 | + sections to lock more than one object at once, because the inner critical | ||
| 2233 | + section may suspend the outer critical sections. This API does not provide | ||
| 2234 | + a way to lock more than two objects at once. | ||
| 2235 | + | ||
| 2236 | + Example usage:: | ||
| 2237 | + | ||
| 2238 | + static PyObject * | ||
| 2239 | + set_field(MyObject *self, PyObject *value) | ||
| 2240 | + { | ||
| 2241 | + Py_BEGIN_CRITICAL_SECTION(self); | ||
| 2242 | + Py_SETREF(self->field, Py_XNewRef(value)); | ||
| 2243 | + Py_END_CRITICAL_SECTION(); | ||
| 2244 | + Py_RETURN_NONE; | ||
| 2245 | + } | ||
| 2246 | + | ||
| 2247 | + In the above example, :c:macro:`Py_SETREF` calls :c:macro:`Py_DECREF`, which | ||
| 2248 | + can call arbitrary code through an object's deallocation function. The critical | ||
| 2249 | + section API avoids potentital deadlocks due to reentrancy and lock ordering | ||
| 2250 | + by allowing the runtime to temporarily suspend the critical section if the | ||
| 2251 | + code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`. | ||
| 2252 | + | ||
| 2253 | + .. c:macro:: Py_BEGIN_CRITICAL_SECTION(op) | ||
| 2254 | + | ||
| 2255 | + Acquires the per-object lock for the object *op* and begins a | ||
| 2256 | + critical section. | ||
| 2257 | + | ||
| 2258 | + In the free-threaded build, this macro expands to:: | ||
| 2259 | + | ||
| 2260 | + { | ||
| 2261 | + PyCriticalSection _py_cs; | ||
| 2262 | + PyCriticalSection_Begin(&_py_cs, (PyObject*)(op)) | ||
| 2263 | + | ||
| 2264 | + In the default build, this macro expands to ``{``. | ||
| 2265 | + | ||
| 2266 | + .. versionadded:: 3.13 | ||
| 2267 | + | ||
| 2268 | + .. c:macro:: Py_END_CRITICAL_SECTION() | ||
| 2269 | + | ||
| 2270 | + Ends the critical section and releases the per-object lock. | ||
| 2271 | + | ||
| 2272 | + In the free-threaded build, this macro expands to:: | ||
| 2273 | + | ||
| 2274 | + PyCriticalSection_End(&_py_cs); | ||
| 2275 | + } | ||
| 2276 | + | ||
| 2277 | + In the default build, this macro expands to ``}``. | ||
| 2278 | + | ||
| 2279 | + .. versionadded:: 3.13 | ||
| 2280 | + | ||
| 2281 | + .. c:macro:: Py_BEGIN_CRITICAL_SECTION2(a, b) | ||
| 2282 | + | ||
| 2283 | + Acquires the per-objects locks for the objects *a* and *b* and begins a | ||
| 2284 | + critical section. The locks are acquired in a consistent order (lowest | ||
| 2285 | + address first) to avoid lock ordering deadlocks. | ||
| 2286 | + | ||
| 2287 | + In the free-threaded build, this macro expands to:: | ||
| 2288 | + | ||
| 2289 | + { | ||
| 2290 | + PyCriticalSection2 _py_cs2; | ||
| 2291 | + PyCriticalSection_Begin2(&_py_cs2, (PyObject*)(a), (PyObject*)(b)) | ||
| 2292 | + | ||
| 2293 | + In the default build, this macro expands to ``{``. | ||
| 2294 | + | ||
| 2295 | + .. versionadded:: 3.13 | ||
| 2296 | + | ||
| 2297 | + .. c:macro:: Py_END_CRITICAL_SECTION2() | ||
| 2298 | + | ||
| 2299 | + Ends the critical section and releases the per-object locks. | ||
| 2300 | + | ||
| 2301 | + In the free-threaded build, this macro expands to:: | ||
| 2302 | + | ||
| 2303 | + PyCriticalSection_End2(&_py_cs2); | ||
| 2304 | + } | ||
| 2305 | + | ||
| 2306 | + In the default build, this macro expands to ``}``. | ||
| 2307 | + | ||
| 2308 | + .. versionadded:: 3.13 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,6 +125,7 @@ | |||
| 125 | 125 | #include "import.h" | |
| 126 | 126 | #include "abstract.h" | |
| 127 | 127 | #include "bltinmodule.h" | |
| 128 | + #include "critical_section.h" | ||
| 128 | 129 | #include "cpython/pyctype.h" | |
| 129 | 130 | #include "pystrtod.h" | |
| 130 | 131 | #include "pystrcmp.h" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,134 @@ | |||
| 1 | + #ifndef Py_CPYTHON_CRITICAL_SECTION_H | ||
| 2 | + # error "this header file must not be included directly" | ||
| 3 | + #endif | ||
| 4 | + | ||
| 5 | + // Python critical sections | ||
| 6 | + // | ||
| 7 | + // Conceptually, critical sections are a deadlock avoidance layer on top of | ||
| 8 | + // per-object locks. These helpers, in combination with those locks, replace | ||
| 9 | + // our usage of the global interpreter lock to provide thread-safety for | ||
| 10 | + // otherwise thread-unsafe objects, such as dict. | ||
| 11 | + // | ||
| 12 | + // NOTE: These APIs are no-ops in non-free-threaded builds. | ||
| 13 | + // | ||
| 14 | + // Straightforward per-object locking could introduce deadlocks that were not | ||
| 15 | + // present when running with the GIL. Threads may hold locks for multiple | ||
| 16 | + // objects simultaneously because Python operations can nest. If threads were | ||
| 17 | + // to acquire the same locks in different orders, they would deadlock. | ||
| 18 | + // | ||
| 19 | + // One way to avoid deadlocks is to allow threads to hold only the lock (or | ||
| 20 | + // locks) for a single operation at a time (typically a single lock, but some | ||
| 21 | + // operations involve two locks). When a thread begins a nested operation it | ||
| 22 | + // could suspend the locks for any outer operation: before beginning the nested | ||
| 23 | + // operation, the locks for the outer operation are released and when the | ||
| 24 | + // nested operation completes, the locks for the outer operation are | ||
| 25 | + // reacquired. | ||
| 26 | + // | ||
| 27 | + // To improve performance, this API uses a variation of the above scheme. | ||
| 28 | + // Instead of immediately suspending locks any time a nested operation begins, | ||
| 29 | + // locks are only suspended if the thread would block. This reduces the number | ||
| 30 | + // of lock acquisitions and releases for nested operations, while still | ||
| 31 | + // avoiding deadlocks. | ||
| 32 | + // | ||
| 33 | + // Additionally, the locks for any active operation are suspended around | ||
| 34 | + // other potentially blocking operations, such as I/O. This is because the | ||
| 35 | + // interaction between locks and blocking operations can lead to deadlocks in | ||
| 36 | + // the same way as the interaction between multiple locks. | ||
| 37 | + // | ||
| 38 | + // Each thread's critical sections and their corresponding locks are tracked in | ||
| 39 | + // a stack in `PyThreadState.critical_section`. When a thread calls | ||
| 40 | + // `_PyThreadState_Detach()`, such as before a blocking I/O operation or when | ||
| 41 | + // waiting to acquire a lock, the thread suspends all of its active critical | ||
| 42 | + // sections, temporarily releasing the associated locks. When the thread calls | ||
| 43 | + // `_PyThreadState_Attach()`, it resumes the top-most (i.e., most recent) | ||
| 44 | + // critical section by reacquiring the associated lock or locks. See | ||
| 45 | + // `_PyCriticalSection_Resume()`. | ||
| 46 | + // | ||
| 47 | + // NOTE: Only the top-most critical section is guaranteed to be active. | ||
| 48 | + // Operations that need to lock two objects at once must use | ||
| 49 | + // `Py_BEGIN_CRITICAL_SECTION2()`. You *CANNOT* use nested critical sections | ||
| 50 | + // to lock more than one object at once, because the inner critical section | ||
| 51 | + // may suspend the outer critical sections. This API does not provide a way | ||
| 52 | + // to lock more than two objects at once (though it could be added later | ||
| 53 | + // if actually needed). | ||
| 54 | + // | ||
| 55 | + // NOTE: Critical sections implicitly behave like reentrant locks because | ||
| 56 | + // attempting to acquire the same lock will suspend any outer (earlier) | ||
| 57 | + // critical sections. However, they are less efficient for this use case than | ||
| 58 | + // purposefully designed reentrant locks. | ||
| 59 | + // | ||
| 60 | + // Example usage: | ||
| 61 | + // Py_BEGIN_CRITICAL_SECTION(op); | ||
| 62 | + // ... | ||
| 63 | + // Py_END_CRITICAL_SECTION(); | ||
| 64 | + // | ||
| 65 | + // To lock two objects at once: | ||
| 66 | + // Py_BEGIN_CRITICAL_SECTION2(op1, op2); | ||
| 67 | + // ... | ||
| 68 | + // Py_END_CRITICAL_SECTION2(); | ||
| 69 | + | ||
| 70 | + typedef struct PyCriticalSection PyCriticalSection; | ||
| 71 | + typedef struct PyCriticalSection2 PyCriticalSection2; | ||
| 72 | + | ||
| 73 | + PyAPI_FUNC(void) | ||
| 74 | + PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op); | ||
| 75 | + | ||
| 76 | + PyAPI_FUNC(void) | ||
| 77 | + PyCriticalSection_End(PyCriticalSection *c); | ||
| 78 | + | ||
| 79 | + PyAPI_FUNC(void) | ||
| 80 | + PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b); | ||
| 81 | + | ||
| 82 | + PyAPI_FUNC(void) | ||
| 83 | + PyCriticalSection2_End(PyCriticalSection2 *c); | ||
| 84 | + | ||
| 85 | + #ifndef Py_GIL_DISABLED | ||
| 86 | + # define Py_BEGIN_CRITICAL_SECTION(op) \ | ||
| 87 | + { | ||
| 88 | + # define Py_END_CRITICAL_SECTION() \ | ||
| 89 | + } | ||
| 90 | + # define Py_BEGIN_CRITICAL_SECTION2(a, b) \ | ||
| 91 | + { | ||
| 92 | + # define Py_END_CRITICAL_SECTION2() \ | ||
| 93 | + } | ||
| 94 | + #else /* !Py_GIL_DISABLED */ | ||
| 95 | + | ||
| 96 | + // NOTE: the contents of this struct are private and may change betweeen | ||
| 97 | + // Python releases without a deprecation period. | ||
| 98 | + struct PyCriticalSection { | ||
| 99 | + // Tagged pointer to an outer active critical section (or 0). | ||
| 100 | + uintptr_t _cs_prev; | ||
| 101 | + | ||
| 102 | + // Mutex used to protect critical section | ||
| 103 | + PyMutex *_cs_mutex; | ||
| 104 | + }; | ||
| 105 | + | ||
| 106 | + // A critical section protected by two mutexes. Use | ||
| 107 | + // Py_BEGIN_CRITICAL_SECTION2 and Py_END_CRITICAL_SECTION2. | ||
| 108 | + // NOTE: the contents of this struct are private and may change betweeen | ||
| 109 | + // Python releases without a deprecation period. | ||
| 110 | + struct PyCriticalSection2 { | ||
| 111 | + PyCriticalSection _cs_base; | ||
| 112 | + | ||
| 113 | + PyMutex *_cs_mutex2; | ||
| 114 | + }; | ||
| 115 | + | ||
| 116 | + # define Py_BEGIN_CRITICAL_SECTION(op) \ | ||
| 117 | + { \ | ||
| 118 | + PyCriticalSection _py_cs; \ | ||
| 119 | + PyCriticalSection_Begin(&_py_cs, _PyObject_CAST(op)) | ||
| 120 | + | ||
| 121 | + # define Py_END_CRITICAL_SECTION() \ | ||
| 122 | + PyCriticalSection_End(&_py_cs); \ | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + # define Py_BEGIN_CRITICAL_SECTION2(a, b) \ | ||
| 126 | + { \ | ||
| 127 | + PyCriticalSection2 _py_cs2; \ | ||
| 128 | + PyCriticalSection2_Begin(&_py_cs2, _PyObject_CAST(a), _PyObject_CAST(b)) | ||
| 129 | + | ||
| 130 | + # define Py_END_CRITICAL_SECTION2() \ | ||
| 131 | + PyCriticalSection2_End(&_py_cs2); \ | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + #endif | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,16 @@ | |||
| 1 | + #ifndef Py_CRITICAL_SECTION_H | ||
| 2 | + #define Py_CRITICAL_SECTION_H | ||
| 3 | + #ifdef __cplusplus | ||
| 4 | + extern "C" { | ||
| 5 | + #endif | ||
| 6 | + | ||
| 7 | + #ifndef Py_LIMITED_API | ||
| 8 | + # define Py_CPYTHON_CRITICAL_SECTION_H | ||
| 9 | + # include "cpython/critical_section.h" | ||
| 10 | + # undef Py_CPYTHON_CRITICAL_SECTION_H | ||
| 11 | + #endif | ||
| 12 | + | ||
| 13 | + #ifdef __cplusplus | ||
| 14 | + } | ||
| 15 | + #endif | ||
| 16 | + #endif /* !Py_CRITICAL_SECTION_H */ | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments