Skip to content

Commit a22a7b6

Browse files
committed
pythongh-117511: Make PyMutex public in the non-limited API
1 parent 6258844 commit a22a7b6

File tree

6 files changed

+103
-67
lines changed

6 files changed

+103
-67
lines changed

Doc/c-api/init.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,3 +1942,29 @@ be used in new code.
19421942
.. c:function:: void PyThread_delete_key_value(int key)
19431943
.. c:function:: void PyThread_ReInitTLS()
19441944
1945+
Synchronization Primitives
1946+
==========================
1947+
1948+
The C-API provides a basic mutual exclusion lock.
1949+
1950+
.. c:type:: PyMutex
1951+
1952+
A mutual exclusion lock. The ``PyMutex`` should be initialized to zero to
1953+
represent the unlocked state.
1954+
1955+
.. versionadded:: 3.13
1956+
1957+
.. c:function:: void PyMutex_Lock(PyMutex *m)
1958+
1959+
Lock the mutex. If another thread has already locked it, the calling
1960+
thread will block until the mutex is unlocked. While blocked, the thread
1961+
will temporarily release the :term:`GIL` if it is held.
1962+
1963+
.. versionadded:: 3.13
1964+
1965+
.. c:function:: void PyMutex_Unlock(PyMutex *m)
1966+
1967+
Unlock the mutex. The mutex must be locked --- otherwise, the function will
1968+
issue a fatal error.
1969+
1970+
.. versionadded:: 3.13

Include/Python.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "pybuffer.h"
5656
#include "pystats.h"
5757
#include "pyatomic.h"
58+
#include "lock.h"
5859
#include "object.h"
5960
#include "objimpl.h"
6061
#include "typeslots.h"

Include/cpython/lock.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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.
9+
//
10+
// Only the two least significant bits are used. The remaining bits should be
11+
// zero:
12+
// 0b00: unlocked
13+
// 0b01: locked
14+
// 0b10: unlocked and has parked threads
15+
// 0b11: locked and has parked threads
16+
//
17+
// Typical initialization:
18+
// PyMutex m = (PyMutex){0};
19+
//
20+
// Or initialize as global variables:
21+
// static PyMutex m;
22+
//
23+
// Typical usage:
24+
// PyMutex_Lock(&m);
25+
// ...
26+
// PyMutex_Unlock(&m);
27+
typedef struct _PyMutex {
28+
uint8_t v;
29+
} PyMutex;
30+
31+
// (private) slow path for locking the mutex
32+
PyAPI_FUNC(void) _PyMutex_LockSlow(PyMutex *m);
33+
34+
// (private) slow path for unlocking the mutex
35+
PyAPI_FUNC(void) _PyMutex_UnlockSlow(PyMutex *m);
36+
37+
// Locks the mutex.
38+
//
39+
// If the mutex is currently locked, the calling thread will be parked until
40+
// the mutex is unlocked. If the current thread holds the GIL, then the GIL
41+
// will be released while the thread is parked.
42+
static inline void
43+
PyMutex_Lock(PyMutex *m)
44+
{
45+
uint8_t expected = _Py_UNLOCKED;
46+
if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) {
47+
_PyMutex_LockSlow(m);
48+
}
49+
}
50+
51+
// Unlocks the mutex.
52+
static inline void
53+
PyMutex_Unlock(PyMutex *m)
54+
{
55+
uint8_t expected = _Py_LOCKED;
56+
if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_UNLOCKED)) {
57+
_PyMutex_UnlockSlow(m);
58+
}
59+
}

Include/internal/pycore_lock.h

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,79 +13,17 @@ extern "C" {
1313
# error "this header requires Py_BUILD_CORE define"
1414
#endif
1515

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+
// NOTE: _Py_UNLOCKED and _Py_LOCKED are defined as 0 and 1
4917
#define _Py_HAS_PARKED 2
5018
#define _Py_ONCE_INITIALIZED 4
5119

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-
5820
static inline int
5921
PyMutex_LockFast(uint8_t *lock_bits)
6022
{
6123
uint8_t expected = _Py_UNLOCKED;
6224
return _Py_atomic_compare_exchange_uint8(lock_bits, &expected, _Py_LOCKED);
6325
}
6426

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-
8927
// Checks if the mutex is currently locked.
9028
static inline int
9129
PyMutex_IsLocked(PyMutex *m)

Include/lock.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff 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 */

Include/object.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ struct _object {
207207
// Create a shared field from a refcnt and desired flags
208208
#define _Py_REF_SHARED(refcnt, flags) (((refcnt) << _Py_REF_SHARED_SHIFT) + (flags))
209209

210-
// NOTE: In non-free-threaded builds, `struct _PyMutex` is defined in
211-
// pycore_lock.h. See pycore_lock.h for more details.
212-
struct _PyMutex { uint8_t v; };
213-
214210
struct _object {
215211
// ob_tid stores the thread id (or zero). It is also used by the GC and the
216212
// trashcan mechanism as a linked list pointer and by the GC to store the

0 commit comments

Comments
 (0)