Browse Source

thread: add generic futex-based muteces

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.
pull/103/head
Rémi Denis-Courmont 7 years ago
parent
commit
0a8a53335b
  1. 55
      include/vlc_threads.h
  2. 124
      src/misc/threads.c

55
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;
const void *_Atomic 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.
*

124
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;

Loading…
Cancel
Save