Skip to content

Commit e75ff35

Browse files
author
Kristján Valur Jónsson
committed
Issue #15038: Optimize python Locks on Windows
Extract cross-platform condition variable support into a separate file and provide user-mode non-recursive locks for Windows.
1 parent 633c4d9 commit e75ff35

File tree

6 files changed

+497
-192
lines changed

6 files changed

+497
-192
lines changed

‎Misc/NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Core and Builtins
2626

2727
- Issue #14673: Add Eric Snow's sys.implementation implementation.
2828

29+
- Issue #15038: Optimize python Locks on Windows.
30+
2931
Library
3032
-------
3133

‎PCbuild/pythoncore.vcxproj‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<ItemGroup Label="ProjectConfigurations">
44
<ProjectConfiguration Include="Debug|Win32">
@@ -481,6 +481,8 @@
481481
<ClInclude Include="..\Parser\tokenizer.h" />
482482
<ClInclude Include="..\PC\errmap.h" />
483483
<ClInclude Include="..\PC\pyconfig.h" />
484+
<ClInclude Include="..\Python\ceval_gil.h" />
485+
<ClInclude Include="..\Python\condvar.h" />
484486
<ClInclude Include="..\Python\importdl.h" />
485487
<ClInclude Include="..\Python\thread_nt.h" />
486488
</ItemGroup>

‎PCbuild/pythoncore.vcxproj.filters‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@
402402
<ClInclude Include="..\Python\thread_nt.h">
403403
<Filter>Python</Filter>
404404
</ClInclude>
405+
<ClInclude Include="..\Include\namespaceobject.h" />
406+
<ClInclude Include="..\Python\condvar.h">
407+
<Filter>Python</Filter>
408+
</ClInclude>
409+
<ClInclude Include="..\Python\ceval_gil.h">
410+
<Filter>Python</Filter>
411+
</ClInclude>
405412
</ItemGroup>
406413
<ItemGroup>
407414
<ClCompile Include="..\Modules\_bisectmodule.c">
@@ -908,6 +915,7 @@
908915
<ClCompile Include="..\Modules\_winapi.c">
909916
<Filter>PC</Filter>
910917
</ClCompile>
918+
<ClCompile Include="..\Objects\namespaceobject.c" />
911919
</ItemGroup>
912920
<ItemGroup>
913921
<ResourceCompile Include="..\PC\python_nt.rc">

‎Python/ceval_gil.h‎

Lines changed: 27 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -59,213 +59,49 @@ static unsigned long gil_interval = DEFAULT_INTERVAL;
5959
(Note: this mechanism is enabled with FORCE_SWITCHING above)
6060
*/
6161

62-
#ifndef _POSIX_THREADS
63-
/* This means pthreads are not implemented in libc headers, hence the macro
64-
not present in unistd.h. But they still can be implemented as an external
65-
library (e.g. gnu pth in pthread emulation) */
66-
# ifdef HAVE_PTHREAD_H
67-
# include <pthread.h> /* _POSIX_THREADS */
68-
# endif
69-
#endif
70-
71-
72-
#ifdef _POSIX_THREADS
73-
74-
/*
75-
* POSIX support
76-
*/
77-
78-
#include <pthread.h>
79-
80-
#define ADD_MICROSECONDS(tv, interval) \
81-
do { \
82-
tv.tv_usec += (long) interval; \
83-
tv.tv_sec += tv.tv_usec / 1000000; \
84-
tv.tv_usec %= 1000000; \
85-
} while (0)
86-
87-
/* We assume all modern POSIX systems have gettimeofday() */
88-
#ifdef GETTIMEOFDAY_NO_TZ
89-
#define GETTIMEOFDAY(ptv) gettimeofday(ptv)
90-
#else
91-
#define GETTIMEOFDAY(ptv) gettimeofday(ptv, (struct timezone *)NULL)
62+
#include "condvar.h"
63+
#ifndef Py_HAVE_CONDVAR
64+
#error You need either a POSIX-compatible or a Windows system!
9265
#endif
9366

94-
#define MUTEX_T pthread_mutex_t
67+
#define MUTEX_T PyMUTEX_T
9568
#define MUTEX_INIT(mut) \
96-
if (pthread_mutex_init(&mut, NULL)) { \
97-
Py_FatalError("pthread_mutex_init(" #mut ") failed"); };
69+
if (PyMUTEX_INIT(&(mut))) { \
70+
Py_FatalError("PyMUTEX_INIT(" #mut ") failed"); };
9871
#define MUTEX_FINI(mut) \
99-
if (pthread_mutex_destroy(&mut)) { \
100-
Py_FatalError("pthread_mutex_destroy(" #mut ") failed"); };
72+
if (PyMUTEX_FINI(&(mut))) { \
73+
Py_FatalError("PyMUTEX_FINI(" #mut ") failed"); };
10174
#define MUTEX_LOCK(mut) \
102-
if (pthread_mutex_lock(&mut)) { \
103-
Py_FatalError("pthread_mutex_lock(" #mut ") failed"); };
75+
if (PyMUTEX_LOCK(&(mut))) { \
76+
Py_FatalError("PyMUTEX_LOCK(" #mut ") failed"); };
10477
#define MUTEX_UNLOCK(mut) \
105-
if (pthread_mutex_unlock(&mut)) { \
106-
Py_FatalError("pthread_mutex_unlock(" #mut ") failed"); };
78+
if (PyMUTEX_UNLOCK(&(mut))) { \
79+
Py_FatalError("PyMUTEX_UNLOCK(" #mut ") failed"); };
10780

108-
#define COND_T pthread_cond_t
81+
#define COND_T PyCOND_T
10982
#define COND_INIT(cond) \
110-
if (pthread_cond_init(&cond, NULL)) { \
111-
Py_FatalError("pthread_cond_init(" #cond ") failed"); };
83+
if (PyCOND_INIT(&(cond))) { \
84+
Py_FatalError("PyCOND_INIT(" #cond ") failed"); };
11285
#define COND_FINI(cond) \
113-
if (pthread_cond_destroy(&cond)) { \
114-
Py_FatalError("pthread_cond_destroy(" #cond ") failed"); };
86+
if (PyCOND_FINI(&(cond))) { \
87+
Py_FatalError("PyCOND_FINI(" #cond ") failed"); };
11588
#define COND_SIGNAL(cond) \
116-
if (pthread_cond_signal(&cond)) { \
117-
Py_FatalError("pthread_cond_signal(" #cond ") failed"); };
89+
if (PyCOND_SIGNAL(&(cond))) { \
90+
Py_FatalError("PyCOND_SIGNAL(" #cond ") failed"); };
11891
#define COND_WAIT(cond, mut) \
119-
if (pthread_cond_wait(&cond, &mut)) { \
120-
Py_FatalError("pthread_cond_wait(" #cond ") failed"); };
92+
if (PyCOND_WAIT(&(cond), &(mut))) { \
93+
Py_FatalError("PyCOND_WAIT(" #cond ") failed"); };
12194
#define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \
12295
{ \
123-
int r; \
124-
struct timespec ts; \
125-
struct timeval deadline; \
126-
\
127-
GETTIMEOFDAY(&deadline); \
128-
ADD_MICROSECONDS(deadline, microseconds); \
129-
ts.tv_sec = deadline.tv_sec; \
130-
ts.tv_nsec = deadline.tv_usec * 1000; \
131-
\
132-
r = pthread_cond_timedwait(&cond, &mut, &ts); \
133-
if (r == ETIMEDOUT) \
96+
int r = PyCOND_TIMEDWAIT(&(cond), &(mut), (microseconds)); \
97+
if (r < 0) \
98+
Py_FatalError("PyCOND_WAIT(" #cond ") failed"); \
99+
if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \
134100
timeout_result = 1; \
135-
else if (r) \
136-
Py_FatalError("pthread_cond_timedwait(" #cond ") failed"); \
137101
else \
138102
timeout_result = 0; \
139103
} \
140104

141-
#elif defined(NT_THREADS)
142-
143-
/*
144-
* Windows (2000 and later, as well as (hopefully) CE) support
145-
*/
146-
147-
#include <windows.h>
148-
149-
#define MUTEX_T CRITICAL_SECTION
150-
#define MUTEX_INIT(mut) do { \
151-
if (!(InitializeCriticalSectionAndSpinCount(&(mut), 4000))) \
152-
Py_FatalError("CreateMutex(" #mut ") failed"); \
153-
} while (0)
154-
#define MUTEX_FINI(mut) \
155-
DeleteCriticalSection(&(mut))
156-
#define MUTEX_LOCK(mut) \
157-
EnterCriticalSection(&(mut))
158-
#define MUTEX_UNLOCK(mut) \
159-
LeaveCriticalSection(&(mut))
160-
161-
/* We emulate condition variables with a semaphore.
162-
We use a Semaphore rather than an auto-reset event, because although
163-
an auto-resent event might appear to solve the lost-wakeup bug (race
164-
condition between releasing the outer lock and waiting) because it
165-
maintains state even though a wait hasn't happened, there is still
166-
a lost wakeup problem if more than one thread are interrupted in the
167-
critical place. A semaphore solves that.
168-
Because it is ok to signal a condition variable with no one
169-
waiting, we need to keep track of the number of
170-
waiting threads. Otherwise, the semaphore's state could rise
171-
without bound.
172-
173-
Generic emulations of the pthread_cond_* API using
174-
Win32 functions can be found on the Web.
175-
The following read can be edificating (or not):
176-
http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
177-
*/
178-
typedef struct COND_T
179-
{
180-
HANDLE sem; /* the semaphore */
181-
int n_waiting; /* how many are unreleased */
182-
} COND_T;
183-
184-
__inline static void _cond_init(COND_T *cond)
185-
{
186-
/* A semaphore with a large max value, The positive value
187-
* is only needed to catch those "lost wakeup" events and
188-
* race conditions when a timed wait elapses.
189-
*/
190-
if (!(cond->sem = CreateSemaphore(NULL, 0, 1000, NULL)))
191-
Py_FatalError("CreateSemaphore() failed");
192-
cond->n_waiting = 0;
193-
}
194-
195-
__inline static void _cond_fini(COND_T *cond)
196-
{
197-
BOOL ok = CloseHandle(cond->sem);
198-
if (!ok)
199-
Py_FatalError("CloseHandle() failed");
200-
}
201-
202-
__inline static void _cond_wait(COND_T *cond, MUTEX_T *mut)
203-
{
204-
++cond->n_waiting;
205-
MUTEX_UNLOCK(*mut);
206-
/* "lost wakeup bug" would occur if the caller were interrupted here,
207-
* but we are safe because we are using a semaphore wich has an internal
208-
* count.
209-
*/
210-
if (WaitForSingleObject(cond->sem, INFINITE) == WAIT_FAILED)
211-
Py_FatalError("WaitForSingleObject() failed");
212-
MUTEX_LOCK(*mut);
213-
}
214-
215-
__inline static int _cond_timed_wait(COND_T *cond, MUTEX_T *mut,
216-
int us)
217-
{
218-
DWORD r;
219-
++cond->n_waiting;
220-
MUTEX_UNLOCK(*mut);
221-
r = WaitForSingleObject(cond->sem, us / 1000);
222-
if (r == WAIT_FAILED)
223-
Py_FatalError("WaitForSingleObject() failed");
224-
MUTEX_LOCK(*mut);
225-
if (r == WAIT_TIMEOUT)
226-
--cond->n_waiting;
227-
/* Here we have a benign race condition with _cond_signal. If the
228-
* wait operation has timed out, but before we can acquire the
229-
* mutex again to decrement n_waiting, a thread holding the mutex
230-
* still sees a positive n_waiting value and may call
231-
* ReleaseSemaphore and decrement n_waiting.
232-
* This will cause n_waiting to be decremented twice.
233-
* This is benign, though, because ReleaseSemaphore will also have
234-
* been called, leaving the semaphore state positive. We may
235-
* thus end up with semaphore in state 1, and n_waiting == -1, and
236-
* the next time someone calls _cond_wait(), that thread will
237-
* pass right through, decrementing the semaphore state and
238-
* incrementing n_waiting, thus correcting the extra _cond_signal.
239-
*/
240-
return r == WAIT_TIMEOUT;
241-
}
242-
243-
__inline static void _cond_signal(COND_T *cond) {
244-
/* NOTE: This must be called with the mutex held */
245-
if (cond->n_waiting > 0) {
246-
if (!ReleaseSemaphore(cond->sem, 1, NULL))
247-
Py_FatalError("ReleaseSemaphore() failed");
248-
--cond->n_waiting;
249-
}
250-
}
251-
252-
#define COND_INIT(cond) \
253-
_cond_init(&(cond))
254-
#define COND_FINI(cond) \
255-
_cond_fini(&(cond))
256-
#define COND_SIGNAL(cond) \
257-
_cond_signal(&(cond))
258-
#define COND_WAIT(cond, mut) \
259-
_cond_wait(&(cond), &(mut))
260-
#define COND_TIMED_WAIT(cond, mut, us, timeout_result) do { \
261-
(timeout_result) = _cond_timed_wait(&(cond), &(mut), us); \
262-
} while (0)
263-
264-
#else
265-
266-
#error You need either a POSIX-compatible or a Windows system!
267-
268-
#endif /* _POSIX_THREADS, NT_THREADS */
269105

270106

271107
/* Whether the GIL is already taken (-1 if uninitialized). This is atomic
@@ -356,13 +192,13 @@ static void drop_gil(PyThreadState *tstate)
356192
MUTEX_LOCK(switch_mutex);
357193
/* Not switched yet => wait */
358194
if (_Py_atomic_load_relaxed(&gil_last_holder) == tstate) {
359-
RESET_GIL_DROP_REQUEST();
195+
RESET_GIL_DROP_REQUEST();
360196
/* NOTE: if COND_WAIT does not atomically start waiting when
361197
releasing the mutex, another thread can run through, take
362198
the GIL and drop it again, and reset the condition
363199
before we even had a chance to wait for it. */
364200
COND_WAIT(switch_cond, switch_mutex);
365-
}
201+
}
366202
MUTEX_UNLOCK(switch_mutex);
367203
}
368204
#endif

0 commit comments

Comments
 (0)