[vlc-devel] [PATCH 2/6] thread: add generic futex-based muteces
RĂ©mi Denis-Courmont
remi at remlab.net
Thu Feb 20 20:58:19 CET 2020
This provides a common implementation of fast (non-debug),
error-checking (debug) and recursive muteces on top of
vlc_atomic_wait() and vlc_atomic_notify_one(), using Drepper's tristate
mutex algorithm.
Benefits of this implementation include:
- Error checking is supported on all platforms (including Windows).
- Initialization can never fail, is not subject to aborts.
- Destruction is a no-op and can be removed altogether.
- Static muteces do not leak or need kludges.
- Ownership can be checked directly without the mutex mark system.
- Non-ownership can be checked in assertion.
Because the player code uses the same vlc_mutex_t typedef for both
non-recursive and recursive usages, disentanglement is not possible.
This patchset thus supports both semantics together as before.
---
include/vlc_threads.h | 55 ++++++++++++++-----
src/misc/threads.c | 124 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 166 insertions(+), 13 deletions(-)
diff --git a/include/vlc_threads.h b/include/vlc_threads.h
index 836f042891..deea618917 100644
--- a/include/vlc_threads.h
+++ b/include/vlc_threads.h
@@ -77,6 +77,7 @@ typedef struct
};
} vlc_mutex_t;
#define VLC_STATIC_MUTEX { false, { { false, 0 } } }
+#define LIBVLC_DONT_WANT_MUTEX
#define LIBVLC_NEED_RWLOCK
typedef INIT_ONCE vlc_once_t;
#define VLC_STATIC_ONCE INIT_ONCE_STATIC_INIT
@@ -125,6 +126,7 @@ typedef struct
};
} vlc_mutex_t;
#define VLC_STATIC_MUTEX { false, { { false, 0 } } }
+#define LIBVLC_DONT_WANT_MUTEX
#define LIBVLC_NEED_RWLOCK
typedef struct
{
@@ -180,6 +182,7 @@ typedef pthread_t vlc_osthread_t;
#define vlc_thread_equal(a,b) pthread_equal(a,b)
typedef pthread_mutex_t vlc_mutex_t;
#define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER
+#define LIBVLC_DONT_WANT_MUTEX
typedef pthread_once_t vlc_once_t;
#define VLC_STATIC_ONCE PTHREAD_ONCE_INIT
typedef pthread_key_t vlc_threadvar_t;
@@ -227,6 +230,7 @@ typedef pthread_t vlc_osthread_t;
#define vlc_thread_equal(a,b) pthread_equal(a,b)
typedef pthread_mutex_t vlc_mutex_t;
#define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER
+#define LIBVLC_DONT_WANT_MUTEX
typedef pthread_rwlock_t vlc_rwlock_t;
#define VLC_STATIC_RWLOCK PTHREAD_RWLOCK_INITIALIZER
typedef pthread_once_t vlc_once_t;
@@ -271,21 +275,9 @@ typedef struct
typedef pthread_t vlc_osthread_t;
#define vlc_thread_equal(a,b) pthread_equal(a,b)
-/**
- * Mutex.
- *
- * Storage space for a mutual exclusion lock.
- *
- * \ingroup mutex
- */
typedef pthread_mutex_t vlc_mutex_t;
-
-/**
- * Static initializer for (static) mutex.
- *
- * \ingroup mutex
- */
#define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER
+#define LIBVLC_DONT_WANT_MUTEX
/**
* Read/write lock.
@@ -343,6 +335,43 @@ typedef struct vlc_timer *vlc_timer_t;
* \defgroup mutex Mutual exclusion locks
* @{
*/
+#ifndef LIBVLC_DONT_WANT_MUTEX
+/**
+ * Mutex.
+ *
+ * Storage space for a mutual exclusion lock.
+ */
+typedef struct
+{
+ union {
+#ifndef __cplusplus
+ struct {
+ atomic_uint value;
+ atomic_uint recursion;
+ _Atomic const void *owner;
+ };
+#endif
+ struct {
+ unsigned int value;
+ unsigned int recursion;
+ const void *owner;
+ } dummy;
+ };
+} vlc_mutex_t;
+
+/**
+ * Static initializer for (static) mutex.
+ *
+ * \note This only works in C code.
+ * In C++, consider using a global \ref vlc::threads::mutex instance instead.
+ */
+#define VLC_STATIC_MUTEX { \
+ .value = ATOMIC_VAR_INIT(0), \
+ .recursion = ATOMIC_VAR_INIT(0), \
+ .owner = ATOMIC_VAR_INIT(NULL), \
+}
+#endif
+
/**
* Initializes a fast mutex.
*
diff --git a/src/misc/threads.c b/src/misc/threads.c
index bdb3c9d687..357687407d 100644
--- a/src/misc/threads.c
+++ b/src/misc/threads.c
@@ -201,6 +201,130 @@ void (vlc_tick_sleep)(vlc_tick_t delay)
}
#endif
+#ifndef LIBVLC_DONT_WANT_MUTEX
+static void vlc_mutex_init_common(vlc_mutex_t *mtx, bool recursive)
+{
+ atomic_init(&mtx->value, 0);
+ atomic_init(&mtx->recursion, recursive);
+ atomic_init(&mtx->owner, NULL);
+}
+
+void vlc_mutex_init(vlc_mutex_t *mtx)
+{
+ vlc_mutex_init_common(mtx, false);
+}
+
+void vlc_mutex_init_recursive(vlc_mutex_t *mtx)
+{
+ vlc_mutex_init_common(mtx, true);
+}
+
+void vlc_mutex_destroy(vlc_mutex_t *mtx)
+{
+ assert(atomic_load_explicit(&mtx->value, memory_order_relaxed) == 0);
+ assert(atomic_load_explicit(&mtx->recursion, memory_order_relaxed) <= 1);
+ assert(atomic_load_explicit(&mtx->owner, memory_order_relaxed) == NULL);
+ (void) mtx;
+}
+
+static _Thread_local char thread_self[1];
+#define THREAD_SELF ((const void *)thread_self)
+
+static bool vlc_mutex_held(const vlc_mutex_t *mtx)
+{
+ /* This comparison is thread-safe:
+ * Even though other threads may modify the owner field at any time,
+ * they will never make ti compare equal to the calling thread.
+ */
+ return THREAD_SELF == atomic_load_explicit(&mtx->owner,
+ memory_order_relaxed);
+}
+
+void vlc_mutex_lock(vlc_mutex_t *mtx)
+{
+ unsigned value;
+
+ /* This is the Drepper (non-recursive) mutex algorithm
+ * from his "Futexes are tricky" paper. The mutex can value be:
+ * - 0: the mutex is free
+ * - 1: the mutex is locked and uncontended
+ * - 2: the mutex is contended (i.e., unlock needs to wake up a waiter)
+ */
+ if (vlc_mutex_trylock(mtx) == 0)
+ return;
+
+ int canc = vlc_savecancel(); /* locking is never a cancellation point */
+
+ while ((value = atomic_exchange_explicit(&mtx->value, 2,
+ memory_order_acquire)) != 0)
+ vlc_atomic_wait(&mtx->value, 2);
+
+ vlc_restorecancel(canc);
+ atomic_store_explicit(&mtx->owner, THREAD_SELF, memory_order_relaxed);
+ vlc_mutex_mark(mtx);
+}
+
+int vlc_mutex_trylock(vlc_mutex_t *mtx)
+{
+ /* Check the recursion counter:
+ * - 0: mutex is not recursive.
+ * - 1: mutex is recursive but free or locked non-recursively.
+ * - n > 1: mutex is recursive and locked n time(s).
+ */
+ unsigned recursion = atomic_load_explicit(&mtx->recursion,
+ memory_order_relaxed);
+ if (unlikely(recursion) && vlc_mutex_held(mtx)) {
+ /* This thread already owns the mutex, locks recursively.
+ * Other threads shall not have modified the recursion or owner fields.
+ */
+ atomic_store_explicit(&mtx->recursion, recursion + 1,
+ memory_order_relaxed);
+ vlc_mutex_mark(mtx);
+ return 0;
+ } else
+ assert(!vlc_mutex_held(mtx));
+
+ unsigned value = 0;
+
+ if (atomic_compare_exchange_strong_explicit(&mtx->value, &value, 1,
+ memory_order_acquire,
+ memory_order_relaxed)) {
+ atomic_store_explicit(&mtx->owner, THREAD_SELF, memory_order_relaxed);
+ vlc_mutex_mark(mtx);
+ return 0;
+ }
+
+ return EBUSY;
+}
+
+void vlc_mutex_unlock(vlc_mutex_t *mtx)
+{
+ assert(vlc_mutex_held(mtx));
+
+ unsigned recursion = atomic_load_explicit(&mtx->recursion,
+ memory_order_relaxed);
+ if (unlikely(recursion > 1)) {
+ /* Non-last recursive unlocking. */
+ atomic_store_explicit(&mtx->recursion, recursion - 1,
+ memory_order_relaxed);
+ vlc_mutex_unmark(mtx);
+ return;
+ }
+
+ atomic_store_explicit(&mtx->owner, NULL, memory_order_relaxed);
+ vlc_mutex_unmark(mtx);
+
+ switch (atomic_exchange_explicit(&mtx->value, 0, memory_order_release)) {
+ case 2:
+ vlc_atomic_notify_one(&mtx->value);
+ case 1:
+ break;
+ default:
+ vlc_assert_unreachable();
+ }
+}
+#endif
+
void vlc_cond_init(vlc_cond_t *cond)
{
cond->head = NULL;
--
2.25.0
More information about the vlc-devel
mailing list