1077 lines
29 KiB
Diff
1077 lines
29 KiB
Diff
From 1968e60cd5d59727bb325d5b69c8f0d7a2f1fe1b Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
|
|
Date: Thu, 14 May 2015 16:08:31 +0200
|
|
Subject: [PATCH] Rewrite the mutex implementation for better performance.
|
|
|
|
This mutex reimplementation attempts to optimise for the common case:
|
|
default mutex type and no contention. Allocation of expensive
|
|
resources (heap memory and Windows kernel objects) is delayed until
|
|
needed, which may be never if the lock isn't subsequently used or
|
|
contended.
|
|
|
|
The global spinlock of the old implementation is gone; performance is
|
|
orders of magnitude better and scales nicely with multiple
|
|
threads. This has been confirmed both in micro-benchmarks and actual
|
|
applications.
|
|
|
|
The reimplementation is fully binary compatible. It does not do nearly
|
|
as much error-checking as the old one, but should be strictly within
|
|
the limits of what Posix allows.
|
|
|
|
Code in thread.c relied on undefined behaviour (pthread_mutex_destroy
|
|
called twice on the same mutex) so an explicit reinitialisation had to
|
|
be added.
|
|
---
|
|
mingw-w64-libraries/winpthreads/src/cond.c | 1 -
|
|
mingw-w64-libraries/winpthreads/src/mutex.c | 609 +++++++++------------------
|
|
mingw-w64-libraries/winpthreads/src/mutex.h | 60 ---
|
|
mingw-w64-libraries/winpthreads/src/ref.c | 1 -
|
|
mingw-w64-libraries/winpthreads/src/sem.c | 1 -
|
|
mingw-w64-libraries/winpthreads/src/sem.h | 3 +-
|
|
mingw-w64-libraries/winpthreads/src/thread.c | 3 +
|
|
7 files changed, 208 insertions(+), 470 deletions(-)
|
|
delete mode 100644 mingw-w64-libraries/winpthreads/src/mutex.h
|
|
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/cond.c b/mingw-w64-libraries/winpthreads/src/cond.c
|
|
index d38513b..7910bb2 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/cond.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/cond.c
|
|
@@ -32,7 +32,6 @@
|
|
#include "pthread_time.h"
|
|
#include "ref.h"
|
|
#include "cond.h"
|
|
-#include "mutex.h"
|
|
#include "thread.h"
|
|
#include "misc.h"
|
|
#include "winpthread_internal.h"
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/mutex.c b/mingw-w64-libraries/winpthreads/src/mutex.c
|
|
index aa3e1ea..fec341a 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/mutex.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/mutex.c
|
|
@@ -1,5 +1,6 @@
|
|
/*
|
|
Copyright (c) 2011, 2014 mingw-w64 project
|
|
+ Copyright (c) 2015 Intel Corporation
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
@@ -21,469 +22,267 @@
|
|
*/
|
|
|
|
#include <windows.h>
|
|
-#include <winternl.h>
|
|
#include <stdio.h>
|
|
#include <malloc.h>
|
|
+#include <stdbool.h>
|
|
#include "pthread.h"
|
|
-#include "ref.h"
|
|
-#include "mutex.h"
|
|
#include "misc.h"
|
|
|
|
-extern int do_sema_b_wait_intern (HANDLE sema, int nointerrupt, DWORD timeout);
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int mutex_static_init(pthread_mutex_t *m);
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int _mutex_trylock(pthread_mutex_t *m);
|
|
-
|
|
-static pthread_spinlock_t mutex_global = PTHREAD_SPINLOCK_INITIALIZER;
|
|
-static pthread_spinlock_t mutex_global_static = PTHREAD_SPINLOCK_INITIALIZER;
|
|
-
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_unref (pthread_mutex_t *m, int r)
|
|
-{
|
|
- mutex_t *m_ = (mutex_t *)*m;
|
|
- pthread_spin_lock (&mutex_global);
|
|
-#ifdef WINPTHREAD_DBG
|
|
- assert((m_->valid == LIFE_MUTEX) && (m_->busy > 0));
|
|
-#endif
|
|
- if (m_->valid == LIFE_MUTEX && m_->busy > 0)
|
|
- m_->busy -= 1;
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return r;
|
|
+typedef enum {
|
|
+ Unlocked, /* Not locked. */
|
|
+ Locked, /* Locked but without waiters. */
|
|
+ Waiting, /* Locked, may have waiters. */
|
|
+} mutex_state_t;
|
|
+
|
|
+typedef enum {
|
|
+ Normal,
|
|
+ Errorcheck,
|
|
+ Recursive,
|
|
+} mutex_type_t;
|
|
+
|
|
+/* The heap-allocated part of a mutex. */
|
|
+typedef struct {
|
|
+ mutex_state_t state;
|
|
+ mutex_type_t type;
|
|
+ HANDLE event; /* Auto-reset event, or NULL if not yet allocated. */
|
|
+ unsigned rec_lock; /* For recursive mutexes, the number of times the
|
|
+ mutex has been locked in excess by the same thread. */
|
|
+ volatile DWORD owner; /* For recursive and error-checking mutexes, the
|
|
+ ID of the owning thread if the mutex is locked. */
|
|
+} mutex_impl_t;
|
|
+
|
|
+/* Whether a mutex is still a static initializer (not a pointer to
|
|
+ a mutex_impl_t). */
|
|
+static bool
|
|
+is_static_initializer(pthread_mutex_t m)
|
|
+{
|
|
+ return (uintptr_t)m >= (uintptr_t)-3;
|
|
}
|
|
|
|
-/* Set the mutex to busy in a thread-safe way */
|
|
-/* A busy mutex can't be destroyed */
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_ref (pthread_mutex_t *m)
|
|
+/* Create and return the implementation part of a mutex from a static
|
|
+ initialiser. Return NULL on out-of-memory error. */
|
|
+static WINPTHREADS_ATTRIBUTE((noinline)) mutex_impl_t *
|
|
+mutex_impl_init(pthread_mutex_t *m, mutex_impl_t *mi)
|
|
{
|
|
- int r = 0;
|
|
-
|
|
- pthread_spin_lock (&mutex_global);
|
|
-
|
|
- if (!m || !*m)
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return EINVAL;
|
|
- }
|
|
-
|
|
- if (STATIC_INITIALIZER(*m))
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- r = mutex_static_init (m);
|
|
- pthread_spin_lock (&mutex_global);
|
|
-
|
|
- if (r != 0 && r != EBUSY)
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return r;
|
|
- }
|
|
+ mutex_impl_t *new_mi = malloc(sizeof(mutex_impl_t));
|
|
+ if (new_mi == NULL)
|
|
+ return NULL;
|
|
+ new_mi->state = Unlocked;
|
|
+ new_mi->type = (mi == PTHREAD_RECURSIVE_MUTEX_INITIALIZER ? Recursive
|
|
+ : mi == PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ? Errorcheck
|
|
+ : Normal);
|
|
+ new_mi->event = NULL;
|
|
+ new_mi->rec_lock = 0;
|
|
+ new_mi->owner = (DWORD)-1;
|
|
+ if (__sync_bool_compare_and_swap(m, mi, new_mi)) {
|
|
+ return new_mi;
|
|
+ } else {
|
|
+ /* Someone created the struct before us. */
|
|
+ free(new_mi);
|
|
+ return *m;
|
|
}
|
|
-
|
|
- r = 0;
|
|
-
|
|
- if (!m || !*m || ((mutex_t *)*m)->valid != LIFE_MUTEX)
|
|
- r = EINVAL;
|
|
- else
|
|
- ((mutex_t *)*m)->busy += 1;
|
|
-
|
|
- pthread_spin_unlock (&mutex_global);
|
|
-
|
|
- return r;
|
|
}
|
|
|
|
-/* An unlock can simply fail with EPERM instead of auto-init (can't be owned) */
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_ref_unlock (pthread_mutex_t *m)
|
|
-{
|
|
- int r = 0;
|
|
- mutex_t *m_ = (mutex_t *)*m;
|
|
-
|
|
- pthread_spin_lock (&mutex_global);
|
|
-
|
|
- if (!m || !*m || ((mutex_t *)*m)->valid != LIFE_MUTEX)
|
|
- r = EINVAL;
|
|
- else if (STATIC_INITIALIZER(*m) || !COND_LOCKED(m_))
|
|
- r = EPERM;
|
|
- else
|
|
- ((mutex_t *)*m)->busy ++;
|
|
-
|
|
- pthread_spin_unlock (&mutex_global);
|
|
-
|
|
- return r;
|
|
-}
|
|
+#define likely(cond) __builtin_expect((cond) != 0, 1)
|
|
+#define unlikely(cond) __builtin_expect((cond) != 0, 0)
|
|
|
|
-/* doesn't lock the mutex but set it to invalid in a thread-safe way */
|
|
-/* A busy mutex can't be destroyed -> EBUSY */
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_ref_destroy (pthread_mutex_t *m, pthread_mutex_t *mDestroy)
|
|
+/* Return the implementation part of a mutex, creating it if necessary.
|
|
+ Return NULL on out-of-memory error. */
|
|
+static inline mutex_impl_t *
|
|
+mutex_impl(pthread_mutex_t *m)
|
|
{
|
|
- pthread_mutex_t mx;
|
|
- mutex_t *m_;
|
|
- int r = 0;
|
|
-
|
|
- if (!m || !*m)
|
|
- return EINVAL;
|
|
-
|
|
- *mDestroy = NULL;
|
|
- /* also considered as busy, any concurrent access prevents destruction: */
|
|
- mx = *m;
|
|
- r = pthread_mutex_trylock (&mx);
|
|
- if (r)
|
|
- return r;
|
|
-
|
|
- pthread_spin_lock (&mutex_global);
|
|
-
|
|
- if (!*m)
|
|
- r = EINVAL;
|
|
- else
|
|
- {
|
|
- m_ = (mutex_t *)*m;
|
|
- if (STATIC_INITIALIZER(*m))
|
|
- *m = NULL;
|
|
- else if (m_->valid != LIFE_MUTEX)
|
|
- r = EINVAL;
|
|
- else if (m_->busy)
|
|
- r = 0xbeef;
|
|
- else
|
|
- {
|
|
- *mDestroy = *m;
|
|
- *m = NULL;
|
|
- }
|
|
- }
|
|
-
|
|
- if (r)
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- pthread_mutex_unlock (&mx);
|
|
- }
|
|
- return r;
|
|
-}
|
|
-
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_ref_init (pthread_mutex_t *m)
|
|
-{
|
|
- int r = 0;
|
|
-
|
|
- pthread_spin_lock (&mutex_global);
|
|
-
|
|
- if (!m) r = EINVAL;
|
|
-
|
|
- if (r)
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return r;
|
|
-}
|
|
-
|
|
-#ifdef WINPTHREAD_DBG
|
|
-static int print_state = 1;
|
|
-
|
|
-void mutex_print_set (int state)
|
|
-{
|
|
- print_state = state;
|
|
-}
|
|
-
|
|
-void mutex_print (volatile pthread_mutex_t *m, char *txt)
|
|
-{
|
|
- if (!print_state)
|
|
- return;
|
|
- mutex_t *m_ = (mutex_t *)*m;
|
|
- if (m_ == NULL) {
|
|
- printf("M%p %d %s\n",*m,(int)GetCurrentThreadId(),txt);
|
|
+ mutex_impl_t *mi = *m;
|
|
+ if (is_static_initializer(mi)) {
|
|
+ return mutex_impl_init(m, mi);
|
|
} else {
|
|
- printf("M%p %d V=%0X B=%d t=%d o=%d C=%d R=%d H=%p %s\n",
|
|
- *m,
|
|
- (int)GetCurrentThreadId(),
|
|
- (int)m_->valid,
|
|
- (int)m_->busy,
|
|
- m_->type,
|
|
- (int)GET_OWNER(m_),(int)(m_->count),(int)GET_RCNT(m_),GET_HANDLE(m_),txt);
|
|
+ /* mi cannot be null here; avoid a test in the fast path. */
|
|
+ if (mi == NULL)
|
|
+ __builtin_unreachable();
|
|
+ return mi;
|
|
}
|
|
}
|
|
-#endif
|
|
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-mutex_static_init (pthread_mutex_t *m)
|
|
-{
|
|
- static pthread_mutexattr_t mxattr_recursive = PTHREAD_MUTEX_RECURSIVE;
|
|
- static pthread_mutexattr_t mxattr_errorcheck = PTHREAD_MUTEX_ERRORCHECK;
|
|
-
|
|
- int r;
|
|
-
|
|
- pthread_spin_lock (&mutex_global_static);
|
|
- if (!STATIC_INITIALIZER(*m))
|
|
- /* Assume someone crept in between: */
|
|
- r = 0;
|
|
- else
|
|
- {
|
|
- if (*m == PTHREAD_MUTEX_INITIALIZER)
|
|
- r = pthread_mutex_init (m, NULL);
|
|
- else if (*m == PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
|
|
- r = pthread_mutex_init (m, &mxattr_recursive);
|
|
- else if (*m == PTHREAD_ERRORCHECK_MUTEX_INITIALIZER)
|
|
- r = pthread_mutex_init (m, &mxattr_errorcheck);
|
|
- else if (*m == NULL)
|
|
- r = EINVAL;
|
|
- else
|
|
- r = pthread_mutex_init (m, NULL);
|
|
- }
|
|
-
|
|
- pthread_spin_unlock (&mutex_global_static);
|
|
- return r;
|
|
-}
|
|
-
|
|
-static int pthread_mutex_lock_intern(pthread_mutex_t *m, DWORD timeout);
|
|
-
|
|
-int
|
|
-pthread_mutex_lock (pthread_mutex_t *m)
|
|
-{
|
|
- return pthread_mutex_lock_intern (m, INFINITE);
|
|
-}
|
|
-
|
|
-static int
|
|
+/* Lock a mutex. Give up after 'timeout' ms (with ETIMEDOUT),
|
|
+ or never if timeout=INFINITE. */
|
|
+static inline int
|
|
pthread_mutex_lock_intern (pthread_mutex_t *m, DWORD timeout)
|
|
{
|
|
- mutex_t *_m;
|
|
- int r;
|
|
- HANDLE h;
|
|
+ mutex_impl_t *mi = mutex_impl(m);
|
|
+ if (mi == NULL)
|
|
+ return ENOMEM;
|
|
+
|
|
+ mutex_state_t old_state = __sync_lock_test_and_set(&mi->state, Locked);
|
|
+ if (unlikely(old_state != Unlocked)) {
|
|
+ /* The mutex is already locked. */
|
|
+
|
|
+ if (mi->type != Normal) {
|
|
+ /* Recursive or Errorcheck */
|
|
+ if (mi->owner == GetCurrentThreadId()) {
|
|
+ /* FIXME: A recursive mutex should not need two atomic ops when locking
|
|
+ recursively. We could rewrite by doing compare-and-swap instead of
|
|
+ test-and-set the first time, but it would lead to more code
|
|
+ duplication and add a conditional branch to the critical path. */
|
|
+ __sync_bool_compare_and_swap(&mi->state, Locked, old_state);
|
|
+ if (mi->type == Recursive) {
|
|
+ mi->rec_lock++;
|
|
+ return 0;
|
|
+ } else {
|
|
+ /* type == Errorcheck */
|
|
+ return EDEADLK;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
- r = mutex_ref (m);
|
|
- if (r)
|
|
- return r;
|
|
+ /* Make sure there is an event object on which to wait. */
|
|
+ if (mi->event == NULL) {
|
|
+ /* Make an auto-reset event object. */
|
|
+ HANDLE ev = CreateEvent(NULL, false, false, NULL);
|
|
+ if (ev == NULL) {
|
|
+ switch (GetLastError()) {
|
|
+ case ERROR_ACCESS_DENIED:
|
|
+ return EPERM;
|
|
+ default:
|
|
+ return ENOMEM; /* Probably accurate enough. */
|
|
+ }
|
|
+ }
|
|
+ if (!__sync_bool_compare_and_swap(&mi->event, NULL, ev)) {
|
|
+ /* Someone created the event before us. */
|
|
+ CloseHandle(ev);
|
|
+ }
|
|
+ }
|
|
|
|
- _m = (mutex_t *) *m;
|
|
- if (_m->type != PTHREAD_MUTEX_NORMAL)
|
|
- {
|
|
- if (COND_LOCKED(_m))
|
|
- {
|
|
- if (COND_OWNER(_m))
|
|
- {
|
|
- if (_m->type == PTHREAD_MUTEX_RECURSIVE)
|
|
- {
|
|
- InterlockedIncrement(&_m->count);
|
|
- return mutex_unref(m,0);
|
|
- }
|
|
-
|
|
- return mutex_unref(m, EDEADLK);
|
|
+ /* At this point, mi->event is non-NULL. */
|
|
+
|
|
+ while (__sync_lock_test_and_set(&mi->state, Waiting) != Unlocked) {
|
|
+ /* For timed locking attempts, it is possible (although unlikely)
|
|
+ that we are woken up but someone else grabs the lock before us,
|
|
+ and we have to go back to sleep again. In that case, the total
|
|
+ wait may be longer than expected. */
|
|
+
|
|
+ unsigned r = WaitForSingleObject(mi->event, timeout);
|
|
+ switch (r) {
|
|
+ case WAIT_TIMEOUT:
|
|
+ return ETIMEDOUT;
|
|
+ case WAIT_OBJECT_0:
|
|
+ break;
|
|
+ default:
|
|
+ return EINVAL;
|
|
}
|
|
}
|
|
}
|
|
-
|
|
- h = _m->h;
|
|
- mutex_unref (m, 0);
|
|
-
|
|
- r = do_sema_b_wait_intern (h, 1, timeout);
|
|
|
|
- if (r != 0)
|
|
- return r;
|
|
-
|
|
- r = mutex_ref (m);
|
|
- if (r)
|
|
- return r;
|
|
+ if (mi->type != Normal)
|
|
+ mi->owner = GetCurrentThreadId();
|
|
|
|
- _m->count = 1;
|
|
- SET_OWNER(_m);
|
|
+ return 0;
|
|
+}
|
|
|
|
- return mutex_unref (m, r);
|
|
+int
|
|
+pthread_mutex_lock (pthread_mutex_t *m)
|
|
+{
|
|
+ return pthread_mutex_lock_intern (m, INFINITE);
|
|
}
|
|
|
|
int pthread_mutex_timedlock(pthread_mutex_t *m, const struct timespec *ts)
|
|
{
|
|
- unsigned long long t, ct;
|
|
- int r;
|
|
- mutex_t *_m;
|
|
-
|
|
- if (!ts)
|
|
- return pthread_mutex_lock(m);
|
|
-
|
|
- r = mutex_ref(m);
|
|
- if (r)
|
|
- return r;
|
|
-
|
|
- /* Try to lock it without waiting */
|
|
- r = _mutex_trylock (m);
|
|
- if (r != EBUSY)
|
|
- return mutex_unref(m,r);
|
|
-
|
|
- _m = (mutex_t *)*m;
|
|
- if (_m->type != PTHREAD_MUTEX_NORMAL && COND_LOCKED(_m) && COND_OWNER(_m))
|
|
- return mutex_unref(m,EDEADLK);
|
|
-
|
|
- ct = _pthread_time_in_ms();
|
|
- t = _pthread_time_in_ms_from_timespec(ts);
|
|
- mutex_unref(m,r);
|
|
- r = pthread_mutex_lock_intern(m, (ct > t ? 0 : (t - ct)));
|
|
- return r;
|
|
+ unsigned long long patience;
|
|
+ if (ts != NULL) {
|
|
+ unsigned long long end = _pthread_time_in_ms_from_timespec(ts);
|
|
+ unsigned long long now = _pthread_time_in_ms();
|
|
+ patience = end > now ? end - now : 0;
|
|
+ if (patience > 0xffffffff)
|
|
+ patience = INFINITE;
|
|
+ } else {
|
|
+ patience = INFINITE;
|
|
+ }
|
|
+ return pthread_mutex_lock_intern(m, patience);
|
|
}
|
|
|
|
int pthread_mutex_unlock(pthread_mutex_t *m)
|
|
{
|
|
- mutex_t *_m;
|
|
- int r = mutex_ref_unlock(m);
|
|
-
|
|
- if(r) {
|
|
- return r;
|
|
- }
|
|
-
|
|
- _m = (mutex_t *)*m;
|
|
-
|
|
- if (_m->type == PTHREAD_MUTEX_NORMAL)
|
|
- {
|
|
- if (!COND_LOCKED(_m))
|
|
- {
|
|
- return mutex_unref(m, EPERM);
|
|
- }
|
|
- }
|
|
- else if (!COND_LOCKED(_m) || !COND_OWNER(_m)) {
|
|
- return mutex_unref(m,EPERM);
|
|
- }
|
|
-
|
|
- if (_m->type == PTHREAD_MUTEX_RECURSIVE)
|
|
- {
|
|
- if(InterlockedDecrement(&_m->count)) {
|
|
- return mutex_unref(m,0);
|
|
- }
|
|
+ /* Here m might an initialiser of an error-checking or recursive mutex, in
|
|
+ which case the behaviour is well-defined, so we can't skip this check. */
|
|
+ mutex_impl_t *mi = mutex_impl(m);
|
|
+ if (mi == NULL)
|
|
+ return ENOMEM;
|
|
+
|
|
+ if (unlikely(mi->type != Normal)) {
|
|
+ if (mi->state == Unlocked)
|
|
+ return EINVAL;
|
|
+ if (mi->owner != GetCurrentThreadId())
|
|
+ return EPERM;
|
|
+ if (mi->rec_lock > 0) {
|
|
+ mi->rec_lock--;
|
|
+ return 0;
|
|
+ }
|
|
+ mi->owner = (DWORD)-1;
|
|
}
|
|
- UNSET_OWNER(_m);
|
|
-
|
|
- if (_m->h != NULL && !ReleaseSemaphore(_m->h, 1, NULL)) {
|
|
- SET_OWNER(_m);
|
|
- /* restore our own bookkeeping */
|
|
- return mutex_unref(m,EPERM);
|
|
+ if (unlikely(__sync_lock_test_and_set(&mi->state, Unlocked) == Waiting)) {
|
|
+ if (!SetEvent(mi->event))
|
|
+ return EPERM;
|
|
}
|
|
-
|
|
- return mutex_unref(m,0);
|
|
+ return 0;
|
|
}
|
|
|
|
-static WINPTHREADS_ATTRIBUTE((noinline)) int
|
|
-_mutex_trylock(pthread_mutex_t *m)
|
|
+int pthread_mutex_trylock(pthread_mutex_t *m)
|
|
{
|
|
- int r = 0;
|
|
- mutex_t *_m = (mutex_t *)*m;
|
|
-
|
|
- if (_m->type != PTHREAD_MUTEX_NORMAL)
|
|
- {
|
|
- if (COND_LOCKED(_m))
|
|
- {
|
|
- if (_m->type == PTHREAD_MUTEX_RECURSIVE && COND_OWNER(_m))
|
|
- {
|
|
- InterlockedIncrement(&_m->count);
|
|
- return 0;
|
|
- }
|
|
-
|
|
- return EBUSY;
|
|
+ mutex_impl_t *mi = mutex_impl(m);
|
|
+ if (mi == NULL)
|
|
+ return ENOMEM;
|
|
+
|
|
+ if (__sync_bool_compare_and_swap(&mi->state, Unlocked, Locked)) {
|
|
+ if (mi->type != Normal)
|
|
+ mi->owner = GetCurrentThreadId();
|
|
+ return 0;
|
|
+ } else {
|
|
+ if (mi->type == Recursive && mi->owner == GetCurrentThreadId()) {
|
|
+ mi->rec_lock++;
|
|
+ return 0;
|
|
}
|
|
- } else if (COND_LOCKED(_m))
|
|
return EBUSY;
|
|
-
|
|
- r = do_sema_b_wait_intern (_m->h, 1, 0);
|
|
-
|
|
- if (r == ETIMEDOUT)
|
|
- r = EBUSY;
|
|
- else if (!r)
|
|
- {
|
|
- _m->count = 1;
|
|
- SET_OWNER(_m);
|
|
}
|
|
-
|
|
- return r;
|
|
-}
|
|
-
|
|
-int pthread_mutex_trylock(pthread_mutex_t *m)
|
|
-{
|
|
- int r = mutex_ref(m);
|
|
- if(r)
|
|
- return r;
|
|
-
|
|
- return mutex_unref(m,_mutex_trylock(m));
|
|
}
|
|
|
|
int
|
|
pthread_mutex_init (pthread_mutex_t *m, const pthread_mutexattr_t *a)
|
|
{
|
|
- mutex_t *_m;
|
|
-
|
|
- int r = mutex_ref_init (m);
|
|
- if (r)
|
|
- return r;
|
|
-
|
|
- if (!(_m = (pthread_mutex_t)calloc(1,sizeof(*_m))))
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return ENOMEM;
|
|
- }
|
|
-
|
|
- _m->type = PTHREAD_MUTEX_DEFAULT;
|
|
- _m->count = 0;
|
|
- _m->busy = 0;
|
|
-
|
|
- if (a)
|
|
- {
|
|
- int share = PTHREAD_PROCESS_PRIVATE;
|
|
- r = pthread_mutexattr_gettype (a, &_m->type);
|
|
- if (!r)
|
|
- r = pthread_mutexattr_getpshared(a, &share);
|
|
-
|
|
- if (!r && share == PTHREAD_PROCESS_SHARED)
|
|
- r = ENOSYS;
|
|
- }
|
|
-
|
|
- if (!r)
|
|
- {
|
|
- if ((_m->h = CreateSemaphore(NULL, 1, 0x7fffffff, NULL)) == NULL)
|
|
- {
|
|
- switch (GetLastError()) {
|
|
- case ERROR_ACCESS_DENIED:
|
|
- r = EPERM;
|
|
- break;
|
|
- default: /* We assume this, to keep it simple: */
|
|
- r = ENOMEM;
|
|
+ pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER;
|
|
+ if (a != NULL) {
|
|
+ int pshared;
|
|
+ if (pthread_mutexattr_getpshared(a, &pshared) == 0
|
|
+ && pshared == PTHREAD_PROCESS_SHARED)
|
|
+ return ENOSYS;
|
|
+
|
|
+ int type;
|
|
+ if (pthread_mutexattr_gettype(a, &type) == 0) {
|
|
+ switch (type) {
|
|
+ case PTHREAD_MUTEX_ERRORCHECK:
|
|
+ init = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER;
|
|
+ break;
|
|
+ case PTHREAD_MUTEX_RECURSIVE:
|
|
+ init = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
|
|
+ break;
|
|
+ default:
|
|
+ init = PTHREAD_MUTEX_INITIALIZER;
|
|
+ break;
|
|
}
|
|
}
|
|
- }
|
|
-
|
|
- if (r)
|
|
- {
|
|
- _m->valid = DEAD_MUTEX;
|
|
- free(_m);
|
|
- *m = NULL;
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return r;
|
|
}
|
|
-
|
|
- _m->valid = LIFE_MUTEX;
|
|
- *m = _m;
|
|
- pthread_spin_unlock (&mutex_global);
|
|
-
|
|
+ *m = init;
|
|
return 0;
|
|
}
|
|
|
|
int pthread_mutex_destroy (pthread_mutex_t *m)
|
|
{
|
|
- mutex_t *_m;
|
|
- pthread_mutex_t mDestroy;
|
|
- int r;
|
|
-
|
|
- while ((r = mutex_ref_destroy (m, &mDestroy)) == 0xbeef)
|
|
- Sleep (0);
|
|
- if (r)
|
|
- return r;
|
|
-
|
|
- if (!mDestroy)
|
|
- {
|
|
- pthread_spin_unlock (&mutex_global);
|
|
- return 0; /* destroyed a (still) static initialized mutex */
|
|
+ mutex_impl_t *mi = *m;
|
|
+ if (!is_static_initializer(mi)) {
|
|
+ if (mi->event != NULL)
|
|
+ CloseHandle(mi->event);
|
|
+ free(mi);
|
|
+ /* Sabotage attempts to re-use the mutex before initialising it again. */
|
|
+ *m = NULL;
|
|
}
|
|
|
|
- /* now the mutex is invalid, and no one can touch it */
|
|
- _m = (mutex_t *)mDestroy;
|
|
-
|
|
- CloseHandle (_m->h);
|
|
- _m->valid = DEAD_MUTEX;
|
|
- _m->type = 0;
|
|
- _m->count = 0;
|
|
- _m->busy = 0;
|
|
- free (mDestroy);
|
|
- *m = NULL;
|
|
- pthread_spin_unlock (&mutex_global);
|
|
-
|
|
return 0;
|
|
}
|
|
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/mutex.h b/mingw-w64-libraries/winpthreads/src/mutex.h
|
|
deleted file mode 100644
|
|
index c8e3cac..0000000
|
|
--- a/mingw-w64-libraries/winpthreads/src/mutex.h
|
|
+++ /dev/null
|
|
@@ -1,60 +0,0 @@
|
|
-/*
|
|
- Copyright (c) 2011 mingw-w64 project
|
|
-
|
|
- Permission is hereby granted, free of charge, to any person obtaining a
|
|
- copy of this software and associated documentation files (the "Software"),
|
|
- to deal in the Software without restriction, including without limitation
|
|
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
- and/or sell copies of the Software, and to permit persons to whom the
|
|
- Software is furnished to do so, subject to the following conditions:
|
|
-
|
|
- The above copyright notice and this permission notice shall be included in
|
|
- all copies or substantial portions of the Software.
|
|
-
|
|
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
- DEALINGS IN THE SOFTWARE.
|
|
-*/
|
|
-
|
|
-#ifndef WIN_PTHREADS_MUTEX_H
|
|
-#define WIN_PTHREADS_MUTEX_H
|
|
-
|
|
-#define COND_LOCKED(m) (m->owner != 0)
|
|
-#define COND_OWNER(m) (m->owner == GetCurrentThreadId())
|
|
-#define COND_DEADLK(m) COND_OWNER(m)
|
|
-#define GET_OWNER(m) (m->owner)
|
|
-#define GET_HANDLE(m) (m->h)
|
|
-#define GET_LOCKCNT(m) (m->count)
|
|
-#define GET_RCNT(m) (m->count) /* not accurate! */
|
|
-#define SET_OWNER(m) (m->owner = GetCurrentThreadId())
|
|
-#define UNSET_OWNER(m) { m->owner = 0; }
|
|
-#define LOCK_UNDO(m)
|
|
-#define COND_DEADLK_NR(m) ((m->type != PTHREAD_MUTEX_RECURSIVE) && COND_DEADLK(m))
|
|
-#define CHECK_DEADLK(m) { if (COND_DEADLK_NR(m)) return EDEADLK; }
|
|
-
|
|
-#define STATIC_INITIALIZER(x) ((intptr_t)(x) >= -3 && (intptr_t)(x) <= -1)
|
|
-#define MUTEX_INITIALIZER2TYPE(x) ((LONGBAG)PTHREAD_NORMAL_MUTEX_INITIALIZER - (LONGBAG)(x))
|
|
-
|
|
-#define LIFE_MUTEX 0xBAB1F00D
|
|
-#define DEAD_MUTEX 0xDEADBEEF
|
|
-
|
|
-typedef struct mutex_t mutex_t;
|
|
-struct mutex_t
|
|
-{
|
|
- LONG valid;
|
|
- volatile LONG busy;
|
|
- int type;
|
|
- volatile LONG count;
|
|
- LONG lockOwner;
|
|
- DWORD owner;
|
|
- HANDLE h;
|
|
-};
|
|
-
|
|
-void mutex_print(volatile pthread_mutex_t *m, char *txt);
|
|
-void mutex_print_set(int state);
|
|
-
|
|
-#endif
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/ref.c b/mingw-w64-libraries/winpthreads/src/ref.c
|
|
index 65d7655..521a033 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/ref.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/ref.c
|
|
@@ -25,7 +25,6 @@
|
|
#include <stdio.h>
|
|
#include "pthread.h"
|
|
#include "semaphore.h"
|
|
-#include "mutex.h"
|
|
#include "rwlock.h"
|
|
#include "cond.h"
|
|
#include "barrier.h"
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/sem.c b/mingw-w64-libraries/winpthreads/src/sem.c
|
|
index d4a9a68..0b8af77 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/sem.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/sem.c
|
|
@@ -28,7 +28,6 @@
|
|
#include "misc.h"
|
|
#include "semaphore.h"
|
|
#include "sem.h"
|
|
-#include "mutex.h"
|
|
#include "ref.h"
|
|
|
|
int do_sema_b_wait_intern (HANDLE sema, int nointerrupt, DWORD timeout);
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/sem.h b/mingw-w64-libraries/winpthreads/src/sem.h
|
|
index b97516e..58eabf9 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/sem.h
|
|
+++ b/mingw-w64-libraries/winpthreads/src/sem.h
|
|
@@ -24,7 +24,6 @@
|
|
#define WIN_SEM
|
|
|
|
#include <windows.h>
|
|
-#include "mutex.h"
|
|
|
|
#define LIFE_SEM 0xBAB1F00D
|
|
#define DEAD_SEM 0xDEADBEEF
|
|
@@ -38,4 +37,4 @@ struct _sem_t
|
|
pthread_mutex_t vlock;
|
|
};
|
|
|
|
-#endif /* WIN_SEM */
|
|
\ No newline at end of file
|
|
+#endif /* WIN_SEM */
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/thread.c b/mingw-w64-libraries/winpthreads/src/thread.c
|
|
index 1f7968f..ceea98e 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/thread.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/thread.c
|
|
@@ -1514,6 +1514,9 @@ pthread_create_wrapper (void *args)
|
|
{
|
|
pthread_mutex_unlock (&tv->p_clock);
|
|
pthread_mutex_destroy (&tv->p_clock);
|
|
+ /* Reinitialise p_clock, since there may be attempts at
|
|
+ destroying it again in __dyn_tls_thread later on. */
|
|
+ tv->p_clock = PTHREAD_MUTEX_INITIALIZER;
|
|
tv->ended = 1;
|
|
}
|
|
while (pthread_mutex_unlock (&mtx_pthr_locked) == 0)
|
|
--
|
|
2.4.5
|
|
|
|
From 249898d9ae310959116efa333e4e3e690cf97452 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
|
|
Date: Thu, 14 May 2015 16:19:34 +0200
|
|
Subject: [PATCH] Rewrite the pthread spinlock implementation.
|
|
|
|
This implementation lets the pthread spinlocks just be spinlocks, as
|
|
they should be. It makes them much faster in every respect.
|
|
|
|
Since these spinlocks are used in many other places in the library,
|
|
those routines have now become measureably faster as well. For
|
|
example, pthread_getspecific has gained substantial speed as a result.
|
|
---
|
|
mingw-w64-libraries/winpthreads/src/spinlock.c | 154 +++----------------------
|
|
1 file changed, 19 insertions(+), 135 deletions(-)
|
|
|
|
diff --git a/mingw-w64-libraries/winpthreads/src/spinlock.c b/mingw-w64-libraries/winpthreads/src/spinlock.c
|
|
index b319863..e55e929 100644
|
|
--- a/mingw-w64-libraries/winpthreads/src/spinlock.c
|
|
+++ b/mingw-w64-libraries/winpthreads/src/spinlock.c
|
|
@@ -1,5 +1,6 @@
|
|
/*
|
|
Copyright (c) 2013 mingw-w64 project
|
|
+ Copyright (c) 2015 Intel Corporation
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
@@ -20,78 +21,22 @@
|
|
DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
-#include <windows.h>
|
|
-#include <stdio.h>
|
|
-#include <malloc.h>
|
|
#include "pthread.h"
|
|
-#include "misc.h"
|
|
|
|
-/* In theory, owner and locks are in CRITICAL_SECTION as well.
|
|
-In practise, however, the implementation of CRITICAL_SECTION
|
|
-should be regarded as opaque. That's why we replicate these
|
|
-members. */
|
|
-typedef struct spin_t {
|
|
- DWORD owner;
|
|
- DWORD locks;
|
|
- CRITICAL_SECTION section;
|
|
-} spin_t;
|
|
+#define likely(cond) __builtin_expect((cond) != 0, 1)
|
|
+#define unlikely(cond) __builtin_expect((cond) != 0, 0)
|
|
|
|
-/* Per the MSDN documentation, Windows components such as the heap manager
|
|
-use a spin count of 4000. */
|
|
-static const DWORD kSpinCount = 4000;
|
|
-
|
|
-static volatile LONG global_lock = 0;
|
|
-
|
|
-static void
|
|
-enter_global_cs (void)
|
|
-{
|
|
- while (global_lock || InterlockedExchange (&global_lock, 1))
|
|
- asm volatile ("pause");
|
|
-}
|
|
-
|
|
-static void
|
|
-leave_global_cs (void)
|
|
-{
|
|
- InterlockedExchange (&global_lock, 0);
|
|
-}
|
|
-
|
|
-static int
|
|
-static_spin_init (pthread_spinlock_t* lock)
|
|
-{
|
|
- if (PTHREAD_SPINLOCK_INITIALIZER == *lock)
|
|
- {
|
|
- enter_global_cs ();
|
|
- if (PTHREAD_SPINLOCK_INITIALIZER == *lock)
|
|
- {
|
|
- int initrv = pthread_spin_init (lock, PTHREAD_PROCESS_PRIVATE);
|
|
- if (initrv < 0)
|
|
- {
|
|
- leave_global_cs ();
|
|
- return initrv;
|
|
- }
|
|
- }
|
|
- leave_global_cs ();
|
|
- }
|
|
- return 0;
|
|
-}
|
|
+/* We use the pthread_spinlock_t itself as a lock:
|
|
+ -1 is free, 0 is locked.
|
|
+ (This is dictated by PTHREAD_SPINLOCK_INITIALIZER, which we can't change
|
|
+ without breaking binary compatibility.) */
|
|
+typedef intptr_t spinlock_word_t;
|
|
|
|
int
|
|
pthread_spin_init (pthread_spinlock_t *lock, int pshared)
|
|
{
|
|
- spin_t *spin;
|
|
-
|
|
- if (!lock)
|
|
- return EINVAL;
|
|
-
|
|
- if (pshared != PTHREAD_PROCESS_PRIVATE)
|
|
- return ENOTSUP;
|
|
-
|
|
- if (!(spin = (spin_t*) calloc (1, sizeof(*spin))))
|
|
- return ENOMEM;
|
|
-
|
|
- InitializeCriticalSection (&spin->section);
|
|
- SetCriticalSectionSpinCount (&spin->section, kSpinCount);
|
|
- *lock = spin;
|
|
+ spinlock_word_t *lk = (spinlock_word_t *)lock;
|
|
+ *lk = -1;
|
|
return 0;
|
|
}
|
|
|
|
@@ -99,93 +44,32 @@ pthread_spin_init (pthread_spinlock_t *lock, int pshared)
|
|
int
|
|
pthread_spin_destroy (pthread_spinlock_t *lock)
|
|
{
|
|
- spin_t *spin;
|
|
-
|
|
- if (!lock || !*lock)
|
|
- return EINVAL;
|
|
-
|
|
- enter_global_cs ();
|
|
- if (*lock == PTHREAD_SPINLOCK_INITIALIZER)
|
|
- {
|
|
- *lock = NULL;
|
|
- leave_global_cs ();
|
|
- return 0;
|
|
- }
|
|
-
|
|
- spin = (spin_t*)*lock;
|
|
- if (spin->owner && spin->owner != GetCurrentThreadId ())
|
|
- {
|
|
- leave_global_cs ();
|
|
- return EPERM;
|
|
- }
|
|
-
|
|
- DeleteCriticalSection (&spin->section);
|
|
- free (spin);
|
|
- *lock = NULL;
|
|
- leave_global_cs ();
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread_spin_lock (pthread_spinlock_t *lock)
|
|
{
|
|
- spin_t *spin;
|
|
- int rv = 0;
|
|
-
|
|
- if (!lock || !*lock)
|
|
- return EINVAL;
|
|
-
|
|
- rv = static_spin_init (lock);
|
|
- if (rv < 0)
|
|
- return rv;
|
|
-
|
|
- spin = (spin_t*)*lock;
|
|
- EnterCriticalSection (&spin->section);
|
|
- spin->owner = GetCurrentThreadId ();
|
|
- spin->locks++;
|
|
+ volatile spinlock_word_t *lk = (volatile spinlock_word_t *)lock;
|
|
+ while (unlikely(__sync_lock_test_and_set(lk, 0) == 0))
|
|
+ do {
|
|
+ asm("pause" ::: "memory");
|
|
+ } while (*lk == 0);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pthread_spin_trylock (pthread_spinlock_t *lock)
|
|
{
|
|
- spin_t *spin;
|
|
- int rv = 0;
|
|
-
|
|
- if (!lock || !*lock)
|
|
- return EINVAL;
|
|
-
|
|
- rv = static_spin_init (lock);
|
|
- if (rv < 0)
|
|
- return rv;
|
|
-
|
|
- spin = (spin_t*)*lock;
|
|
- if (!TryEnterCriticalSection (&spin->section))
|
|
- return EBUSY;
|
|
-
|
|
- spin->owner = GetCurrentThreadId ();
|
|
- spin->locks++;
|
|
- return 0;
|
|
+ spinlock_word_t *lk = (spinlock_word_t *)lock;
|
|
+ return __sync_lock_test_and_set(lk, 0) == 0 ? EBUSY : 0;
|
|
}
|
|
|
|
|
|
int
|
|
pthread_spin_unlock (pthread_spinlock_t *lock)
|
|
{
|
|
- spin_t *spin;
|
|
-
|
|
- if (!lock || !*lock)
|
|
- return EINVAL;
|
|
-
|
|
- if (*lock == PTHREAD_SPINLOCK_INITIALIZER)
|
|
- return EPERM;
|
|
-
|
|
- spin = (spin_t*)*lock;
|
|
- if (spin->owner != GetCurrentThreadId ())
|
|
- return EPERM;
|
|
-
|
|
- if (!--spin->locks)
|
|
- spin->owner = 0;
|
|
- LeaveCriticalSection (&spin->section);
|
|
+ volatile spinlock_word_t *lk = (volatile spinlock_word_t *)lock;
|
|
+ *lk = -1;
|
|
return 0;
|
|
}
|
|
--
|
|
2.4.5
|
|
|