Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions mcslock/mcslock.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <stdatomic.h>
#include <stddef.h>

#include "mcslock.h"
Expand All @@ -14,34 +15,37 @@ enum { MCS_PROCEED = 0, MCS_WAIT = 1 };
#define spin_wait() ((void) 0)
#endif

static inline void wait_until_equal_u8(uint8_t *loc, uint8_t val, int mm)
static inline void wait_until_equal_u8(_Atomic uint8_t *loc,
uint8_t val,
memory_order mm)
{
while (__atomic_load_n(loc, mm) != val)
while (atomic_load_explicit(loc, mm) != val)
spin_wait();
}

void mcslock_init(mcslock_t *lock)
{
*lock = NULL;
atomic_init(lock, NULL);
}

void mcslock_acquire(mcslock_t *lock, mcsnode_t *node)
{
node->next = NULL;
atomic_init(&node->next, NULL);
/* A0: Read and write lock, synchronized with A0/A1 */
mcsnode_t *prev = __atomic_exchange_n(lock, node, __ATOMIC_ACQ_REL);
mcsnode_t *prev =
atomic_exchange_explicit(lock, node, memory_order_acq_rel);
if (LIKELY(!prev)) /* Lock uncontended, the lock is acquired */
return;
/* Otherwise, the lock is owned by another thread, waiting for its turn */

node->wait = MCS_WAIT;
atomic_store_explicit(&node->wait, MCS_WAIT, memory_order_release);
/* B0: Write next, synchronized with B1/B2 */
__atomic_store_n(&prev->next, node, __ATOMIC_RELEASE);
atomic_store_explicit(&prev->next, node, memory_order_release);

/* Waiting for the previous thread to signal using the assigned node
* C0: Read wait, synchronized with C1
*/
wait_until_equal_u8(&node->wait, MCS_PROCEED, __ATOMIC_ACQUIRE);
wait_until_equal_u8(&node->wait, MCS_PROCEED, memory_order_acquire);
}

void mcslock_release(mcslock_t *lock, mcsnode_t *node)
Expand All @@ -50,26 +54,28 @@ void mcslock_release(mcslock_t *lock, mcsnode_t *node)

/* Check if any waiting thread exists */
/* B1: Read next, synchronized with B0 */
if ((next = __atomic_load_n(&node->next, __ATOMIC_ACQUIRE)) == NULL) {
if ((next = atomic_load_explicit(&node->next, memory_order_acquire)) ==
NULL) {
/* No waiting threads detected, attempt lock release */
/* Use temporary variable as it might be overwritten */
mcsnode_t *tmp = node;

/* A1: write lock, synchronize with A0 */
if (__atomic_compare_exchange_n(lock, &tmp, NULL, 0, __ATOMIC_RELEASE,
__ATOMIC_RELAXED)) {
if (atomic_compare_exchange_strong_explicit(
lock, &tmp, NULL, memory_order_release, memory_order_relaxed)) {
/* No waiting threads yet, lock released successfully */
return;
}
/* Otherwise, at least one waiting thread exists */

/* Wait for the first waiting thread to link its node with ours */
/* B2: Read next, synchronized with B0 */
while ((next = __atomic_load_n(&node->next, __ATOMIC_ACQUIRE)) == NULL)
while ((next = atomic_load_explicit(&node->next,
memory_order_acquire)) == NULL)
spin_wait();
}

/* Signal the first waiting thread */
/* C1: Write wait, synchronized with C0 */
__atomic_store_n(&next->wait, MCS_PROCEED, __ATOMIC_RELEASE);
atomic_store_explicit(&next->wait, MCS_PROCEED, memory_order_release);
}
7 changes: 4 additions & 3 deletions mcslock/mcslock.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#pragma once

#include <stdatomic.h>
#include <stdint.h>

typedef struct mcsnode {
struct mcsnode *next;
uint8_t wait;
_Atomic(struct mcsnode *) next;
_Atomic(uint8_t) wait;
} mcsnode_t;

typedef mcsnode_t *mcslock_t;
typedef _Atomic(mcsnode_t*) mcslock_t;

/* Initialize an MCS lock */
void mcslock_init(mcslock_t *lock);
Expand Down