gh-108724: Add PyMutex and _PyParkingLot APIs (gh-109344) · sthagen/python-cpython@0c89056 · GitHub
Skip to content

Commit 0c89056

Browse files
pythongh-108724: Add PyMutex and _PyParkingLot APIs (pythongh-109344)
PyMutex is a one byte lock with fast, inlineable lock and unlock functions for the common uncontended case. The design is based on WebKit's WTF::Lock. PyMutex is built using the _PyParkingLot APIs, which provides a cross-platform futex-like API (based on WebKit's WTF::ParkingLot). This internal API will be used for building other synchronization primitives used to implement PEP 703, such as one-time initialization and events. This also includes tests and a mini benchmark in Tools/lockbench/lockbench.py to compare with the existing PyThread_type_lock. Uncontended acquisition + release: * Linux (x86-64): PyMutex: 11 ns, PyThread_type_lock: 44 ns * macOS (arm64): PyMutex: 13 ns, PyThread_type_lock: 18 ns * Windows (x86-64): PyMutex: 13 ns, PyThread_type_lock: 38 ns PR Overview: The primary purpose of this PR is to implement PyMutex, but there are a number of support pieces (described below). * PyMutex: A 1-byte lock that doesn't require memory allocation to initialize and is generally faster than the existing PyThread_type_lock. The API is internal only for now. * _PyParking_Lot: A futex-like API based on the API of the same name in WebKit. Used to implement PyMutex. * _PyRawMutex: A word sized lock used to implement _PyParking_Lot. * PyEvent: A one time event. This was used a bunch in the "nogil" fork and is useful for testing the PyMutex implementation, so I've included it as part of the PR. * pycore_llist.h: Defines common operations on doubly-linked list. Not strictly necessary (could do the list operations manually), but they come up frequently in the "nogil" fork. ( Similar to https://man.freebsd.org/cgi/man.cgi?queue) --------- Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
1 parent 0a31ff0 commit 0c89056

29 files changed

Lines changed: 1665 additions & 21 deletions

Include/Python.h

Lines changed: 1 addition & 0 deletions

Include/cpython/pyatomic.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@
8383
// # release
8484
// ...
8585

86-
#ifndef Py_ATOMIC_H
87-
#define Py_ATOMIC_H
88-
86+
#ifndef Py_CPYTHON_ATOMIC_H
87+
# error "this header file must not be included directly"
88+
#endif
8989

9090
// --- _Py_atomic_add --------------------------------------------------------
9191
// Atomically adds `value` to `obj` and returns the previous value
@@ -501,6 +501,3 @@ static inline void _Py_atomic_fence_release(void);
501501
#else
502502
# error "no available pyatomic implementation for this platform/compiler"
503503
#endif
504-
505-
#endif /* Py_ATOMIC_H */
506-

Include/cpython/pyatomic_msc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ _Py_atomic_store_ptr_release(void *obj, void *value)
906906
#if defined(_M_X64) || defined(_M_IX86)
907907
*(void * volatile *)obj = value;
908908
#elif defined(_M_ARM64)
909-
__stlr64(obj, (uintptr_t)value);
909+
__stlr64((unsigned __int64 volatile *)obj, (uintptr_t)value);
910910
#else
911911
# error "no implementation of _Py_atomic_store_ptr_release"
912912
#endif

Include/internal/pycore_llist.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// A doubly-linked list that can be embedded in a struct.
2+
//
3+
// Usage:
4+
// struct llist_node head = LLIST_INIT(head);
5+
// typedef struct {
6+
// ...
7+
// struct llist_node node;
8+
// ...
9+
// } MyObj;
10+
//
11+
// llist_insert_tail(&head, &obj->node);
12+
// llist_remove(&obj->node);
13+
//
14+
// struct llist_node *node;
15+
// llist_for_each(node, &head) {
16+
// MyObj *obj = llist_data(node, MyObj, node);
17+
// ...
18+
// }
19+
//
20+
21+
#ifndef Py_INTERNAL_LLIST_H
22+
#define Py_INTERNAL_LLIST_H
23+
24+
#include <stddef.h>
25+
26+
#ifdef __cplusplus
27+
extern "C" {
28+
#endif
29+
30+
#ifndef Py_BUILD_CORE
31+
# error "Py_BUILD_CORE must be defined to include this header"
32+
#endif
33+
34+
struct llist_node {
35+
struct llist_node *next;
36+
struct llist_node *prev;
37+
};
38+
39+
// Get the struct containing a node.
40+
#define llist_data(node, type, member) \
41+
(type*)((char*)node - offsetof(type, member))
42+
43+
// Iterate over a list.
44+
#define llist_for_each(node, head) \
45+
for (node = (head)->next; node != (head); node = node->next)
46+
47+
// Iterate over a list, but allow removal of the current node.
48+
#define llist_for_each_safe(node, head) \
49+
for (struct llist_node *_next = (node = (head)->next, node->next); \
50+
node != (head); node = _next, _next = node->next)
51+
52+
#define LLIST_INIT(head) { &head, &head }
53+
54+
static inline void
55+
llist_init(struct llist_node *head)
56+
{
57+
head->next = head;
58+
head->prev = head;
59+
}
60+
61+
// Returns 1 if the list is empty, 0 otherwise.
62+
static inline int
63+
llist_empty(struct llist_node *head)
64+
{
65+
return head->next == head;
66+
}
67+
68+
// Appends to the tail of the list.
69+
static inline void
70+
llist_insert_tail(struct llist_node *head, struct llist_node *node)
71+
{
72+
node->prev = head->prev;
73+
node->next = head;
74+
head->prev->next = node;
75+
head->prev = node;
76+
}
77+
78+
// Remove a node from the list.
79+
static inline void
80+
llist_remove(struct llist_node *node)
81+
{
82+
struct llist_node *prev = node->prev;
83+
struct llist_node *next = node->next;
84+
prev->next = next;
85+
next->prev = prev;
86+
node->prev = NULL;
87+
node->next = NULL;
88+
}
89+
90+
// Append all nodes from head2 onto head1. head2 is left empty.
91+
static inline void
92+
llist_concat(struct llist_node *head1, struct llist_node *head2)
93+
{
94+
if (!llist_empty(head2)) {
95+
head1->prev->next = head2->next;
96+
head2->next->prev = head1->prev;
97+
98+
head1->prev = head2->prev;
99+
head2->prev->next = head1;
100+
llist_init(head2);
101+
}
102+
}
103+
104+
#ifdef __cplusplus
105+
}
106+
#endif
107+
#endif /* !Py_INTERNAL_LLIST_H */

Include/internal/pycore_lock.h

Lines changed: 158 additions & 0 deletions

0 commit comments

Comments
 (0)