[vlc-devel] [PATCH 2/3] threads: rewrite R/W lock to avoid contention
remi at remlab.net
remi at remlab.net
Tue Jan 12 17:30:32 UTC 2021
From: RĂ©mi Denis-Courmont <remi at remlab.net>
The current implementation would cause threads to sleep upon concurrent
read lock/unlock. This fixes that. Sleeping now only occurs when writers
are involved.
---
include/vlc_threads.h | 17 +++++++--
src/misc/threads.c | 88 +++++++++++++++++++++++++------------------
2 files changed, 65 insertions(+), 40 deletions(-)
diff --git a/include/vlc_threads.h b/include/vlc_threads.h
index 2054e632f0..0004ee0ef0 100644
--- a/include/vlc_threads.h
+++ b/include/vlc_threads.h
@@ -532,15 +532,26 @@ VLC_API int vlc_sem_timedwait(vlc_sem_t *sem, vlc_tick_t deadline) VLC_USED;
*/
typedef struct vlc_rwlock
{
+ union {
+#ifndef __cplusplus
+ struct {
+ atomic_uint state;
+ atomic_uint wait;
+ };
+#endif
+ struct {
+ unsigned state;
+ unsigned wait;
+ } dummy;
+ };
vlc_mutex_t mutex;
- vlc_cond_t wait;
- long state;
} vlc_rwlock_t;
/**
* Static initializer for (static) read/write lock.
*/
-#define VLC_STATIC_RWLOCK { VLC_STATIC_MUTEX, VLC_STATIC_COND, 0 }
+#define VLC_STATIC_RWLOCK \
+ { { { ATOMIC_VAR_INIT(0), ATOMIC_VAR_INIT(0) } }, VLC_STATIC_MUTEX }
/**
* Initializes a read/write lock.
diff --git a/src/misc/threads.c b/src/misc/threads.c
index 76c7049f00..a1da0edae5 100644
--- a/src/misc/threads.c
+++ b/src/misc/threads.c
@@ -373,22 +373,16 @@ int vlc_cond_timedwait_daytime(vlc_cond_t *cond, vlc_mutex_t *mutex,
}
/*** Generic read/write locks ***/
-/* NOTE:
- * lock->state is a signed long integer:
- * - The sign bit is set when the lock is held for writing.
- * - The other bits code the number of times the lock is held for reading.
- * Consequently:
- * - The value is negative if and only if the lock is held for writing.
- * - The value is zero if and only if the lock is not held at all.
+/* vlc_rwlock_t::state counts the number of outstanding read-side locks.
+ * UINT_MAX means lock is held for writing.
*/
-#define READER_MASK LONG_MAX
-#define WRITER_BIT LONG_MIN
+static int vlc_rwlock_tryrdlock(vlc_rwlock_t *lock);
void vlc_rwlock_init (vlc_rwlock_t *lock)
{
+ atomic_init(&lock->state, 0);
+ atomic_init(&lock->wait, 0);
vlc_mutex_init (&lock->mutex);
- vlc_cond_init (&lock->wait);
- lock->state = 0;
}
void vlc_rwlock_destroy (vlc_rwlock_t *lock)
@@ -398,48 +392,68 @@ void vlc_rwlock_destroy (vlc_rwlock_t *lock)
void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
{
- vlc_mutex_lock (&lock->mutex);
- /* Recursive read-locking is allowed.
- * Ensure that there is no active writer. */
- while (lock->state < 0)
- {
- assert (lock->state == WRITER_BIT);
- vlc_cond_wait (&lock->wait, &lock->mutex);
- }
- if (unlikely(lock->state >= READER_MASK))
+ while (vlc_rwlock_tryrdlock(lock))
+ vlc_atomic_wait(&lock->state, UINT_MAX);
+}
+
+static int vlc_rwlock_tryrdlock(vlc_rwlock_t *lock)
+{
+ unsigned state = atomic_load_explicit(&lock->state, memory_order_relaxed);
+
+ do
+ if (state == UINT_MAX)
+ return EBUSY;
+ while (!atomic_compare_exchange_weak_explicit(&lock->state, &state,
+ state + 1,
+ memory_order_acquire,
+ memory_order_relaxed));
+
+ if (unlikely(state == UINT_MAX - 1))
abort (); /* An overflow is certainly a recursion bug. */
- lock->state++;
- vlc_mutex_unlock (&lock->mutex);
+
+ return 0;
}
void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
{
vlc_mutex_lock (&lock->mutex);
- /* Wait until nobody owns the lock in any way. */
- while (lock->state != 0)
- vlc_cond_wait (&lock->wait, &lock->mutex);
- lock->state = WRITER_BIT;
- vlc_mutex_unlock (&lock->mutex);
+
+ for (;;) {
+ unsigned exp = 0;
+
+ atomic_store_explicit(&lock->wait, 1, memory_order_seq_cst);
+
+ if (atomic_compare_exchange_strong_explicit(&lock->state, &exp,
+ UINT_MAX,
+ memory_order_acquire,
+ memory_order_relaxed))
+ break;
+
+ vlc_atomic_wait(&lock->wait, 1);
+ }
}
void vlc_rwlock_unlock (vlc_rwlock_t *lock)
{
- vlc_mutex_lock (&lock->mutex);
- if (lock->state < 0)
+ unsigned state = atomic_load_explicit(&lock->state, memory_order_relaxed);
+
+ if (state == UINT_MAX)
{ /* Write unlock */
- assert (lock->state == WRITER_BIT);
- /* Let reader and writer compete. OS scheduler decides who wins. */
- lock->state = 0;
- vlc_cond_broadcast (&lock->wait);
+ atomic_store_explicit(&lock->wait, 0, memory_order_relaxed);
+ atomic_store_explicit(&lock->state, 0, memory_order_release);
+ vlc_atomic_notify_all(&lock->state); /* Wake waiting readers */
+ vlc_mutex_unlock(&lock->mutex); /* Wake one waiting writer */
}
else
{ /* Read unlock */
- assert (lock->state > 0);
+ assert(state != 0);
+
/* If there are no readers left, wake up one pending writer. */
- if (--lock->state == 0)
- vlc_cond_signal (&lock->wait);
+ if (atomic_fetch_sub_explicit(&lock->state, 1,
+ memory_order_acq_rel) == 1
+ && atomic_exchange_explicit(&lock->wait, 0, memory_order_seq_cst))
+ vlc_atomic_notify_one(&lock->wait);
}
- vlc_mutex_unlock (&lock->mutex);
}
/*** Generic semaphores ***/
--
2.30.0
More information about the vlc-devel
mailing list