/*****************************************************************************
* threads.c: LibVLC generic thread support
*****************************************************************************
* Copyright (C) 2009-2016 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdatomic.h>
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_atomic.h>
#include "../libvlc.h"
#include "threads.h"
/* <stdatomic.h> types cannot be used in the C++ view of <vlc_threads.h> */
struct vlc_suuint { union { unsigned int value; }; };
static_assert (sizeof (atomic_uint) <= sizeof (struct vlc_suuint),
"Size mismatch");
static_assert (alignof (atomic_uint) <= alignof (struct vlc_suuint),
"Alignment mismatch");
/*** Global locks ***/
void vlc_global_mutex (unsigned n, bool acquire)
{
static vlc_mutex_t locks[] = {
VLC_STATIC_MUTEX,
VLC_STATIC_MUTEX,
VLC_STATIC_MUTEX,
VLC_STATIC_MUTEX,
#ifdef _WIN32
VLC_STATIC_MUTEX, // For MTA holder
#endif
};
static_assert (VLC_MAX_MUTEX == ARRAY_SIZE(locks),
"Wrong number of global mutexes");
assert (n < ARRAY_SIZE(locks));
vlc_mutex_t *lock = locks + n;
if (acquire)
vlc_mutex_lock (lock);
else
vlc_mutex_unlock (lock);
}
static void vlc_mutex_init_common(vlc_mutex_t *mtx, bool recursive)
{
atomic_init(&mtx->value, 0);
atomic_init(&mtx->recursion, recursive ? 1 : 0);
atomic_init(&mtx->owner, 0);
}
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);
}
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 it compare equal to the calling thread.
*/
return vlc_thread_id() == atomic_load_explicit(&mtx->owner,
memory_order_relaxed);
}
void vlc_mutex_lock(vlc_mutex_t *mtx)
{
/* This is the Drepper (non-recursive) mutex algorithm
* from his "Futexes are tricky" paper. The mutex value can 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 (atomic_exchange_explicit(&mtx->value, 2, memory_order_acquire))
vlc_atomic_wait(&mtx->value, 2);
vlc_restorecancel(canc);
atomic_store_explicit(&mtx->owner, vlc_thread_id(), memory_order_relaxed);
}
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)) {
if (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);
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, vlc_thread_id(),
memory_order_relaxed);
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);
return;
}
atomic_store_explicit(&mtx->owner, 0, memory_order_relaxed);
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();
}
}
void vlc_cond_init(vlc_cond_t *cond)
{
cond->head = NULL;
vlc_mutex_init(&cond->lock);
}
struct vlc_cond_waiter {
struct vlc_cond_waiter **pprev, *next;
atomic_uint value;
};
static void vlc_cond_signal_waiter(struct vlc_cond_waiter *waiter)
{
waiter->pprev = &waiter->next;
waiter->next = NULL;
atomic_fetch_add_explicit(&waiter->value, 1, memory_order_relaxed);
vlc_atomic_notify_one(&waiter->value);
}
void vlc_cond_signal(vlc_cond_t *cond)
{
struct vlc_cond_waiter *waiter;
/* Some call sites signal their condition variable without holding the
* corresponding lock. Thus an extra lock is needed here to ensure the
* consistency of the linked list and the lifetime of its elements.
* If all call sites locked cleanly, the inner lock would be unnecessary.
*/
vlc_mutex_lock(&cond->lock);
waiter = cond->head;
if (waiter != NULL) {
struct vlc_cond_waiter *next = waiter->next;
struct vlc_cond_waiter **pprev = waiter->pprev;
*pprev = next;
if (next != NULL)
next->pprev = pprev;
vlc_cond_signal_waiter(waiter);
}
vlc_mutex_unlock(&cond->lock);
}
void vlc_cond_broadcast(vlc_cond_t *cond)
{
struct vlc_cond_waiter *waiter;
vlc_mutex_lock(&cond->lock);
waiter = cond->head;
cond->head = NULL;
/* Keep the lock here so that waiters don't go out of scope */
while (waiter != NULL) {
struct vlc_cond_waiter *next = waiter->next;
vlc_cond_signal_waiter(waiter);
waiter = next;
}
vlc_mutex_unlock(&cond->lock);
}
static void vlc_cond_wait_prepare(struct vlc_cond_waiter *waiter,
vlc_cond_t *cond, vlc_mutex_t *mutex)
{
struct vlc_cond_waiter *next;
waiter->pprev = &cond->head;
atomic_init(&waiter->value, 0);
vlc_mutex_lock(&cond->lock);
next = cond->head;
cond->head = waiter;
waiter->next = next;
if (next != NULL)
next->pprev = &waiter->next;
vlc_mutex_unlock(&cond->lock);
vlc_mutex_unlock(mutex);
}
static void vlc_cond_wait_finish(struct vlc_cond_waiter *waiter,
vlc_cond_t *cond, vlc_mutex_t *mutex)
{
struct vlc_cond_waiter *next;
/* If this waiter is still on the linked list, remove it before it goes
* out of scope. Otherwise, this is a no-op.
*/
vlc_mutex_lock(&cond->lock);
next = waiter->next;
*(waiter->pprev) = next;
if (next != NULL)
next->pprev = waiter->pprev;
vlc_mutex_unlock(&cond->lock);
/* Lock the caller's mutex as required by condition variable semantics. */
vlc_mutex_lock(mutex);
}
void vlc_cond_wait(vlc_cond_t *cond, vlc_mutex_t *mutex)
{
struct vlc_cond_waiter waiter;
// wait on a multiply locked mutex not supported
assert(atomic_load_explicit(&mutex->recursion, memory_order_relaxed) <= 1);
vlc_cond_wait_prepare(&waiter, cond, mutex);
vlc_atomic_wait(&waiter.value, 0);
vlc_cond_wait_finish(&waiter, cond, mutex);
}
int vlc_cond_timedwait(vlc_cond_t *cond, vlc_mutex_t *mutex,
vlc_tick_t deadline)
{
struct vlc_cond_waiter waiter;
int ret;
// wait on a multiply locked mutex not supported
assert(atomic_load_explicit(&mutex->recursion, memory_order_relaxed) <= 1);
vlc_cond_wait_prepare(&waiter, cond, mutex);
ret = vlc_atomic_timedwait(&waiter.value, 0, deadline);
vlc_cond_wait_finish(&waiter, cond, mutex);
return ret;
}
/*** Generic semaphores ***/
void vlc_sem_init (vlc_sem_t *sem, unsigned value)
{
atomic_init(&sem->value, value);
}
int vlc_sem_post (vlc_sem_t *sem)
{
unsigned exp = atomic_load_explicit(&sem->value, memory_order_relaxed);
do
{
if (unlikely(exp == UINT_MAX))
return EOVERFLOW;
} while (!atomic_compare_exchange_weak_explicit(&sem->value, &exp, exp + 1,
memory_order_release,
memory_order_relaxed));
vlc_atomic_notify_one(&sem->value);
return 0;
}
void vlc_sem_wait (vlc_sem_t *sem)
{
unsigned exp = 1;
while (!atomic_compare_exchange_weak_explicit(&sem->value, &exp, exp - 1,
memory_order_acquire,
memory_order_relaxed))
{
if (likely(exp == 0))
{
vlc_atomic_wait(&sem->value, 0);
exp = 1;
}
}
}
int vlc_sem_timedwait(vlc_sem_t *sem, vlc_tick_t deadline)
{
unsigned exp = 1;
while (!atomic_compare_exchange_weak_explicit(&sem->value, &exp, exp - 1,
memory_order_acquire,
memory_order_relaxed))
{
if (likely(exp == 0))
{
int ret = vlc_atomic_timedwait(&sem->value, 0, deadline);
if (ret)
return ret;
exp = 1;
}
}
return 0;
}
int vlc_sem_trywait(vlc_sem_t *sem)
{
unsigned exp = atomic_load_explicit(&sem->value, memory_order_relaxed);
do
if (exp == 0)
return EAGAIN;
while (!atomic_compare_exchange_weak_explicit(&sem->value, &exp, exp - 1,
memory_order_acquire,
memory_order_relaxed));
return 0;
}
enum { VLC_LATCH_READY, VLC_LATCH_CONTEND, VLC_LATCH_PENDING };
void vlc_latch_init(vlc_latch_t *latch, size_t value)
{
atomic_init(&latch->ready, value ? VLC_LATCH_PENDING : VLC_LATCH_READY);
atomic_init(&latch->value, value);
}
static bool vlc_latch_count_down_ready(vlc_latch_t *latch, size_t n)
{
size_t value;
value = atomic_fetch_sub_explicit(&latch->value, n, memory_order_acq_rel);
assert(value >= n);
if (value != n)
return false;
if (atomic_exchange_explicit(&latch->ready, VLC_LATCH_READY,
memory_order_release) == VLC_LATCH_CONTEND)
vlc_atomic_notify_all(&latch->ready);
return true;
}
void vlc_latch_count_down(vlc_latch_t *latch, size_t n)
{
(void) vlc_latch_count_down_ready(latch, n);
}
void vlc_latch_count_down_and_wait(vlc_latch_t *latch, size_t n)
{
if (!vlc_latch_count_down_ready(latch, n))
vlc_latch_wait(latch);
}
bool vlc_latch_is_ready(const vlc_latch_t *latch)
{
return atomic_load_explicit(&latch->ready,
memory_order_acquire) == VLC_LATCH_READY;
}
void vlc_latch_wait(vlc_latch_t *latch)
{
unsigned int expected = VLC_LATCH_PENDING;
if (!atomic_compare_exchange_strong_explicit(&latch->ready, &expected,
VLC_LATCH_CONTEND,
memory_order_acquire,
memory_order_acquire)) {
if (expected == VLC_LATCH_READY)
return;
assert(expected == VLC_LATCH_CONTEND);
}
do
vlc_atomic_wait(&latch->ready, VLC_LATCH_CONTEND);
while (!vlc_latch_is_ready(latch));
}
void vlc_queuedmutex_init(vlc_queuedmutex_t *m)
{
atomic_init(&m->head, 0);
atomic_init(&m->tail, 0);
atomic_init(&m->owner, 0);
}
bool vlc_queuedmutex_held(vlc_queuedmutex_t *m)
{
return (vlc_thread_id() == atomic_load_explicit(&m->owner, memory_order_relaxed));
}
void vlc_queuedmutex_lock(vlc_queuedmutex_t *m)
{
uint_fast32_t ticket = atomic_fetch_add_explicit(&m->tail, 1,
memory_order_relaxed);
uint_fast32_t head;
while ((head = atomic_load_explicit(&m->head,
memory_order_acquire)) != ticket)
vlc_atomic_wait(&m->head, head);
atomic_store_explicit(&m->owner, vlc_thread_id(), memory_order_relaxed);
}
void vlc_queuedmutex_unlock(vlc_queuedmutex_t *m)
{
atomic_store_explicit(&m->owner, 0, memory_order_relaxed);
atomic_fetch_add_explicit(&m->head, 1, memory_order_release);
vlc_atomic_notify_all(&m->head);
}
enum { VLC_ONCE_UNDONE, VLC_ONCE_DOING, VLC_ONCE_CONTEND, VLC_ONCE_DONE };
static_assert (VLC_ONCE_DONE == 3, "Check vlc_once in header file");
bool (vlc_once_begin)(vlc_once_t *restrict once)
{
unsigned int value = VLC_ONCE_UNDONE;
if (atomic_compare_exchange_strong_explicit(&once->value, &value,
VLC_ONCE_DOING,
memory_order_acquire,
memory_order_acquire))
return false;
assert(value >= VLC_ONCE_DOING);
if (unlikely(value == VLC_ONCE_DOING)
&& atomic_compare_exchange_strong_explicit(&once->value, &value,
VLC_ONCE_CONTEND,
memory_order_acquire,
memory_order_acquire))
value = VLC_ONCE_CONTEND;
assert(value >= VLC_ONCE_CONTEND);
while (unlikely(value != VLC_ONCE_DONE)) {
vlc_atomic_wait(&once->value, VLC_ONCE_CONTEND);
value = atomic_load_explicit(&once->value, memory_order_acquire);
}
return true;
}
void vlc_once_complete(vlc_once_t *restrict once)
{
switch (atomic_exchange_explicit(&once->value, VLC_ONCE_DONE,
memory_order_release)) {
case VLC_ONCE_DOING: /* No waiters, nothing (else) to do */
break;
case VLC_ONCE_CONTEND: /* Notify waiters */
vlc_atomic_notify_all(&once->value);
break;
default:
vlc_assert_unreachable();
}
}
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|