| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,6 +55,11 @@ The following functions can be safely called before Python is initialized: | |||
| 55 | 55 | * :c:func:`PyMem_RawCalloc` | |
| 56 | 56 | * :c:func:`PyMem_RawFree` | |
| 57 | 57 | ||
| 58 | + * Synchronization: | ||
| 59 | + | ||
| 60 | + * :c:func:`PyMutex_Lock` | ||
| 61 | + * :c:func:`PyMutex_Unlock` | ||
| 62 | + | ||
| 58 | 63 | .. note:: | |
| 59 | 64 | ||
| 60 | 65 | The following functions **should not be called** before | |
@@ -2152,3 +2157,41 @@ be used in new code. | |||
| 2152 | 2157 | .. c:function:: void PyThread_delete_key_value(int key) | |
| 2153 | 2158 | .. c:function:: void PyThread_ReInitTLS() | |
| 2154 | 2159 | ||
| 2160 | + Synchronization Primitives | ||
| 2161 | + ========================== | ||
| 2162 | + | ||
| 2163 | + The C-API provides a basic mutual exclusion lock. | ||
| 2164 | + | ||
| 2165 | + .. c:type:: PyMutex | ||
| 2166 | + | ||
| 2167 | + A mutual exclusion lock. The :c:type:`!PyMutex` should be initialized to | ||
| 2168 | + zero to represent the unlocked state. For example:: | ||
| 2169 | + | ||
| 2170 | + PyMutex mutex = {0}; | ||
| 2171 | + | ||
| 2172 | + Instances of :c:type:`!PyMutex` should not be copied or moved. Both the | ||
| 2173 | + contents and address of a :c:type:`!PyMutex` are meaningful, and it must | ||
| 2174 | + remain at a fixed, writable location in memory. | ||
| 2175 | + | ||
| 2176 | + .. note:: | ||
| 2177 | + | ||
| 2178 | + A :c:type:`!PyMutex` currently occupies one byte, but the size should be | ||
| 2179 | + considered unstable. The size may change in future Python releases | ||
| 2180 | + without a deprecation period. | ||
| 2181 | + | ||
| 2182 | + .. versionadded:: 3.13 | ||
| 2183 | + | ||
| 2184 | + .. c:function:: void PyMutex_Lock(PyMutex *m) | ||
| 2185 | + | ||
| 2186 | + Lock mutex *m*. If another thread has already locked it, the calling | ||
| 2187 | + thread will block until the mutex is unlocked. While blocked, the thread | ||
| 2188 | + will temporarily release the :term:`GIL` if it is held. | ||
| 2189 | + | ||
| 2190 | + .. versionadded:: 3.13 | ||
| 2191 | + | ||
| 2192 | + .. c:function:: void PyMutex_Unlock(PyMutex *m) | ||
| 2193 | + | ||
| 2194 | + Unlock mutex *m*. The mutex must be locked --- otherwise, the function will | ||
| 2195 | + issue a fatal error. | ||
| 2196 | + | ||
| 2197 | + .. versionadded:: 3.13 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2172,6 +2172,11 @@ New Features | |||
| 2172 | 2172 | :c:func:`PyEval_GetLocals` return :term:`strong references <strong reference>` | |
| 2173 | 2173 | rather than borrowed references. (Added as part of :pep:`667`.) | |
| 2174 | 2174 | ||
| 2175 | + * Add :c:type:`PyMutex` API, a lightweight mutex that occupies a single byte. | ||
| 2176 | + The :c:func:`PyMutex_Lock` function will release the GIL (if currently held) | ||
| 2177 | + if the operation needs to block. | ||
| 2178 | + (Contributed by Sam Gross in :gh:`108724`.) | ||
| 2179 | + | ||
| 2175 | 2180 | Build Changes | |
| 2176 | 2181 | ============= | |
| 2177 | 2182 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -64,6 +64,7 @@ | |||
| 64 | 64 | #include "pybuffer.h" | |
| 65 | 65 | #include "pystats.h" | |
| 66 | 66 | #include "pyatomic.h" | |
| 67 | + #include "lock.h" | ||
| 67 | 68 | #include "object.h" | |
| 68 | 69 | #include "refcount.h" | |
| 69 | 70 | #include "objimpl.h" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,63 @@ | |||
| 1 | + #ifndef Py_CPYTHON_LOCK_H | ||
| 2 | + # error "this header file must not be included directly" | ||
| 3 | + #endif | ||
| 4 | + | ||
| 5 | + #define _Py_UNLOCKED 0 | ||
| 6 | + #define _Py_LOCKED 1 | ||
| 7 | + | ||
| 8 | + // A mutex that occupies one byte. The lock can be zero initialized to | ||
| 9 | + // represent the unlocked state. | ||
| 10 | + // | ||
| 11 | + // Typical initialization: | ||
| 12 | + // PyMutex m = (PyMutex){0}; | ||
| 13 | + // | ||
| 14 | + // Or initialize as global variables: | ||
| 15 | + // static PyMutex m; | ||
| 16 | + // | ||
| 17 | + // Typical usage: | ||
| 18 | + // PyMutex_Lock(&m); | ||
| 19 | + // ... | ||
| 20 | + // PyMutex_Unlock(&m); | ||
| 21 | + // | ||
| 22 | + // The contents of the PyMutex are not part of the public API, but are | ||
| 23 | + // described to aid in understanding the implementation and debugging. Only | ||
| 24 | + // the two least significant bits are used. The remaining bits are always zero: | ||
| 25 | + // 0b00: unlocked | ||
| 26 | + // 0b01: locked | ||
| 27 | + // 0b10: unlocked and has parked threads | ||
| 28 | + // 0b11: locked and has parked threads | ||
| 29 | + typedef struct PyMutex { | ||
| 30 | + uint8_t _bits; // (private) | ||
| 31 | + } PyMutex; | ||
| 32 | + | ||
| 33 | + // exported function for locking the mutex | ||
| 34 | + PyAPI_FUNC(void) PyMutex_Lock(PyMutex *m); | ||
| 35 | + | ||
| 36 | + // exported function for unlocking the mutex | ||
| 37 | + PyAPI_FUNC(void) PyMutex_Unlock(PyMutex *m); | ||
| 38 | + | ||
| 39 | + // Locks the mutex. | ||
| 40 | + // | ||
| 41 | + // If the mutex is currently locked, the calling thread will be parked until | ||
| 42 | + // the mutex is unlocked. If the current thread holds the GIL, then the GIL | ||
| 43 | + // will be released while the thread is parked. | ||
| 44 | + static inline void | ||
| 45 | + _PyMutex_Lock(PyMutex *m) | ||
| 46 | + { | ||
| 47 | + uint8_t expected = _Py_UNLOCKED; | ||
| 48 | + if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_LOCKED)) { | ||
| 49 | + PyMutex_Lock(m); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + #define PyMutex_Lock _PyMutex_Lock | ||
| 53 | + | ||
| 54 | + // Unlocks the mutex. | ||
| 55 | + static inline void | ||
| 56 | + _PyMutex_Unlock(PyMutex *m) | ||
| 57 | + { | ||
| 58 | + uint8_t expected = _Py_LOCKED; | ||
| 59 | + if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_UNLOCKED)) { | ||
| 60 | + PyMutex_Unlock(m); | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + #define PyMutex_Unlock _PyMutex_Unlock | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,7 +36,7 @@ struct _PyWeakReference { | |||
| 36 | 36 | * Normally this can be derived from wr_object, but in some cases we need | |
| 37 | 37 | * to lock after wr_object has been set to Py_None. | |
| 38 | 38 | */ | |
| 39 | - struct _PyMutex *weakrefs_lock; | ||
| 39 | + PyMutex *weakrefs_lock; | ||
| 40 | 40 | #endif | |
| 41 | 41 | }; | |
| 42 | 42 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -202,7 +202,7 @@ _PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2, | |||
| 202 | 202 | static inline void | |
| 203 | 203 | _PyCriticalSection_Begin(_PyCriticalSection *c, PyMutex *m) | |
| 204 | 204 | { | |
| 205 | - if (PyMutex_LockFast(&m->v)) { | ||
| 205 | + if (PyMutex_LockFast(&m->_bits)) { | ||
| 206 | 206 | PyThreadState *tstate = _PyThreadState_GET(); | |
| 207 | 207 | c->mutex = m; | |
| 208 | 208 | c->prev = tstate->critical_section; | |
@@ -255,8 +255,8 @@ _PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2) | |||
| 255 | 255 | m2 = tmp; | |
| 256 | 256 | } | |
| 257 | 257 | ||
| 258 | - if (PyMutex_LockFast(&m1->v)) { | ||
| 259 | - if (PyMutex_LockFast(&m2->v)) { | ||
| 258 | + if (PyMutex_LockFast(&m1->_bits)) { | ||
| 259 | + if (PyMutex_LockFast(&m2->_bits)) { | ||
| 260 | 260 | PyThreadState *tstate = _PyThreadState_GET(); | |
| 261 | 261 | c->base.mutex = m1; | |
| 262 | 262 | c->mutex2 = m2; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,84 +13,22 @@ extern "C" { | |||
| 13 | 13 | # error "this header requires Py_BUILD_CORE define" | |
| 14 | 14 | #endif | |
| 15 | 15 | ||
| 16 | - | ||
| 17 | - // A mutex that occupies one byte. The lock can be zero initialized. | ||
| 18 | - // | ||
| 19 | - // Only the two least significant bits are used. The remaining bits should be | ||
| 20 | - // zero: | ||
| 21 | - // 0b00: unlocked | ||
| 22 | - // 0b01: locked | ||
| 23 | - // 0b10: unlocked and has parked threads | ||
| 24 | - // 0b11: locked and has parked threads | ||
| 25 | - // | ||
| 26 | - // Typical initialization: | ||
| 27 | - // PyMutex m = (PyMutex){0}; | ||
| 28 | - // | ||
| 29 | - // Or initialize as global variables: | ||
| 30 | - // static PyMutex m; | ||
| 31 | - // | ||
| 32 | - // Typical usage: | ||
| 33 | - // PyMutex_Lock(&m); | ||
| 34 | - // ... | ||
| 35 | - // PyMutex_Unlock(&m); | ||
| 36 | - | ||
| 37 | - // NOTE: In Py_GIL_DISABLED builds, `struct _PyMutex` is defined in Include/object.h. | ||
| 38 | - // The Py_GIL_DISABLED builds need the definition in Include/object.h for the | ||
| 39 | - // `ob_mutex` field in PyObject. For the default (non-free-threaded) build, | ||
| 40 | - // we define the struct here to avoid exposing it in the public API. | ||
| 41 | - #ifndef Py_GIL_DISABLED | ||
| 42 | - struct _PyMutex { uint8_t v; }; | ||
| 43 | - #endif | ||
| 44 | - | ||
| 45 | - typedef struct _PyMutex PyMutex; | ||
| 46 | - | ||
| 47 | - #define _Py_UNLOCKED 0 | ||
| 48 | - #define _Py_LOCKED 1 | ||
| 16 | + //_Py_UNLOCKED is defined as 0 and _Py_LOCKED as 1 in Include/cpython/lock.h | ||
| 49 | 17 | #define _Py_HAS_PARKED 2 | |
| 50 | 18 | #define _Py_ONCE_INITIALIZED 4 | |
| 51 | 19 | ||
| 52 | - // (private) slow path for locking the mutex | ||
| 53 | - PyAPI_FUNC(void) _PyMutex_LockSlow(PyMutex *m); | ||
| 54 | - | ||
| 55 | - // (private) slow path for unlocking the mutex | ||
| 56 | - PyAPI_FUNC(void) _PyMutex_UnlockSlow(PyMutex *m); | ||
| 57 | - | ||
| 58 | 20 | static inline int | |
| 59 | 21 | PyMutex_LockFast(uint8_t *lock_bits) | |
| 60 | 22 | { | |
| 61 | 23 | uint8_t expected = _Py_UNLOCKED; | |
| 62 | 24 | return _Py_atomic_compare_exchange_uint8(lock_bits, &expected, _Py_LOCKED); | |
| 63 | 25 | } | |
| 64 | 26 | ||
| 65 | - // Locks the mutex. | ||
| 66 | - // | ||
| 67 | - // If the mutex is currently locked, the calling thread will be parked until | ||
| 68 | - // the mutex is unlocked. If the current thread holds the GIL, then the GIL | ||
| 69 | - // will be released while the thread is parked. | ||
| 70 | - static inline void | ||
| 71 | - PyMutex_Lock(PyMutex *m) | ||
| 72 | - { | ||
| 73 | - uint8_t expected = _Py_UNLOCKED; | ||
| 74 | - if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) { | ||
| 75 | - _PyMutex_LockSlow(m); | ||
| 76 | - } | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - // Unlocks the mutex. | ||
| 80 | - static inline void | ||
| 81 | - PyMutex_Unlock(PyMutex *m) | ||
| 82 | - { | ||
| 83 | - uint8_t expected = _Py_LOCKED; | ||
| 84 | - if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_UNLOCKED)) { | ||
| 85 | - _PyMutex_UnlockSlow(m); | ||
| 86 | - } | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | 27 | // Checks if the mutex is currently locked. | |
| 90 | 28 | static inline int | |
| 91 | 29 | PyMutex_IsLocked(PyMutex *m) | |
| 92 | 30 | { | |
| 93 | - return (_Py_atomic_load_uint8(&m->v) & _Py_LOCKED) != 0; | ||
| 31 | + return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0; | ||
| 94 | 32 | } | |
| 95 | 33 | ||
| 96 | 34 | // Re-initializes the mutex after a fork to the unlocked state. | |
@@ -121,7 +59,7 @@ static inline void | |||
| 121 | 59 | PyMutex_LockFlags(PyMutex *m, _PyLockFlags flags) | |
| 122 | 60 | { | |
| 123 | 61 | uint8_t expected = _Py_UNLOCKED; | |
| 124 | - if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) { | ||
| 62 | + if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_LOCKED)) { | ||
| 125 | 63 | _PyMutex_LockTimed(m, -1, flags); | |
| 126 | 64 | } | |
| 127 | 65 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,7 +14,7 @@ struct _warnings_runtime_state { | |||
| 14 | 14 | PyObject *filters; /* List */ | |
| 15 | 15 | PyObject *once_registry; /* Dict */ | |
| 16 | 16 | PyObject *default_action; /* String */ | |
| 17 | - struct _PyMutex mutex; | ||
| 17 | + PyMutex mutex; | ||
| 18 | 18 | long filters_version; | |
| 19 | 19 | }; | |
| 20 | 20 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,16 @@ | |||
| 1 | + #ifndef Py_LOCK_H | ||
| 2 | + #define Py_LOCK_H | ||
| 3 | + #ifdef __cplusplus | ||
| 4 | + extern "C" { | ||
| 5 | + #endif | ||
| 6 | + | ||
| 7 | + #ifndef Py_LIMITED_API | ||
| 8 | + # define Py_CPYTHON_LOCK_H | ||
| 9 | + # include "cpython/lock.h" | ||
| 10 | + # undef Py_CPYTHON_LOCK_H | ||
| 11 | + #endif | ||
| 12 | + | ||
| 13 | + #ifdef __cplusplus | ||
| 14 | + } | ||
| 15 | + #endif | ||
| 16 | + #endif /* !Py_LOCK_H */ | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -137,17 +137,13 @@ struct _object { | |||
| 137 | 137 | // fields have been merged. | |
| 138 | 138 | #define _Py_UNOWNED_TID 0 | |
| 139 | 139 | ||
| 140 | - // NOTE: In non-free-threaded builds, `struct _PyMutex` is defined in | ||
| 141 | - // pycore_lock.h. See pycore_lock.h for more details. | ||
| 142 | - struct _PyMutex { uint8_t v; }; | ||
| 143 | - | ||
| 144 | 140 | struct _object { | |
| 145 | 141 | // ob_tid stores the thread id (or zero). It is also used by the GC and the | |
| 146 | 142 | // trashcan mechanism as a linked list pointer and by the GC to store the | |
| 147 | 143 | // computed "gc_refs" refcount. | |
| 148 | 144 | uintptr_t ob_tid; | |
| 149 | 145 | uint16_t _padding; | |
| 150 | - struct _PyMutex ob_mutex; // per-object lock | ||
| 146 | + PyMutex ob_mutex; // per-object lock | ||
| 151 | 147 | uint8_t ob_gc_bits; // gc-related state | |
| 152 | 148 | uint32_t ob_ref_local; // local reference count | |
| 153 | 149 | Py_ssize_t ob_ref_shared; // shared (atomic) reference count | |
| Back | FazBrowse Home | New Git URL |
0 commit comments