This repository has been archived on 2025-02-04. You can view files and clone it, but cannot push or open issues or pull requests.
helpless/oogabooga/concurrency.c

182 lines
5 KiB
C
Raw Normal View History

typedef struct Spinlock Spinlock;
typedef struct Mutex Mutex;
typedef struct Binary_Semaphore Binary_Semaphore;
// These are probably your best friend for sync-free multi-processing.
inline bool compare_and_swap_8(volatile uint8_t *a, uint8_t b, uint8_t old);
inline bool compare_and_swap_16(volatile uint16_t *a, uint16_t b, uint16_t old);
inline bool compare_and_swap_32(volatile uint32_t *a, uint32_t b, uint32_t old);
inline bool compare_and_swap_64(volatile uint64_t *a, uint64_t b, uint64_t old);
inline bool compare_and_swap_bool(volatile bool *a, bool b, bool old);
///
// Spinlock "primitive"
// Like a mutex but it eats up the entire core while waiting.
// Beneficial if contention is low or sync speed is important
typedef struct Spinlock {
volatile bool locked;
} Spinlock;
void ogb_instance
spinlock_init(Spinlock *l);
void ogb_instance
spinlock_acquire_or_wait(Spinlock* l);
// This returns true if successfully acquired or false if timeout reached.
bool ogb_instance
spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds);
void ogb_instance
spinlock_release(Spinlock* l);
///
// High-level mutex primitive (short spinlock then OS mutex lock)
// Just spins for a few (configurable) microseconds with a spinlock,
// and if acquiring fails it falls back to a OS mutex.
#define MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS 100
typedef struct Mutex {
Spinlock spinlock;
f64 spin_time_microseconds;
Mutex_Handle os_handle;
volatile bool spinlock_acquired;
volatile u64 acquiring_thread;
} Mutex;
void ogb_instance
mutex_init(Mutex *m);
void ogb_instance
mutex_destroy(Mutex *m);
void ogb_instance
mutex_acquire_or_wait(Mutex *m);
void ogb_instance
mutex_release(Mutex *m);
///
// Binary semaphore
typedef struct Binary_Semaphore {
volatile bool signaled;
Mutex mutex;
} Binary_Semaphore;
void ogb_instance
binary_semaphore_init(Binary_Semaphore *sem, bool initial_state);
void ogb_instance
binary_semaphore_destroy(Binary_Semaphore *sem);
void ogb_instance
binary_semaphore_wait(Binary_Semaphore *sem);
void ogb_instance
binary_semaphore_signal(Binary_Semaphore *sem);
#if !OOGABOOGA_LINK_EXTERNAL_INSTANCE
void spinlock_init(Spinlock *l) {
memset(l, 0, sizeof(*l));
}
void spinlock_acquire_or_wait(Spinlock* l) {
while (true) {
bool expected = false;
if (compare_and_swap_bool(&l->locked, true, expected)) {
return;
}
while (l->locked) {
// spinny boi
}
}
}
// Returns true on aquired, false if timeout seconds reached
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds) {
f64 start = os_get_elapsed_seconds();
while (true) {
bool expected = false;
if (compare_and_swap_bool(&l->locked, true, expected)) {
return true;
}
while (l->locked) {
// spinny boi
if ((os_get_elapsed_seconds()-start) >= timeout_seconds) return false;
}
}
return true;
}
void spinlock_release(Spinlock* l) {
bool expected = true;
bool success = compare_and_swap_bool(&l->locked, false, expected);
2024-07-15 21:40:27 +02:00
assert(success, "This thread should have acquired the spinlock but compare_and_swap failed");
}
///
// High-level mutex primitive (short spinlock then OS mutex lock)
void mutex_init(Mutex *m) {
spinlock_init(&m->spinlock);
m->spin_time_microseconds = MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS;
m->os_handle = os_make_mutex();
m->spinlock_acquired = false;
m->acquiring_thread = 0;
}
void mutex_destroy(Mutex *m) {
os_destroy_mutex(m->os_handle);
}
void mutex_acquire_or_wait(Mutex *m) {
if (spinlock_acquire_or_wait_timeout(&m->spinlock, m->spin_time_microseconds / 1000000.0)) {
assert(!m->spinlock_acquired, "Internal sync error in Mutex");
m->spinlock_acquired = true;
}
os_lock_mutex(m->os_handle);
assert(!m->acquiring_thread, "Internal sync error in Mutex: Multiple threads acquired");
m->acquiring_thread = context.thread_id;
}
void mutex_release(Mutex *m) {
assert(m->acquiring_thread != 0, "Tried to release a mutex which is not acquired");
assert(m->acquiring_thread == context.thread_id, "Non-owning thread tried to release mutex");
m->acquiring_thread = 0;
bool was_spinlock_acquired = m->spinlock_acquired;
m->spinlock_acquired = false;
os_unlock_mutex(m->os_handle);
if (was_spinlock_acquired) {
spinlock_release(&m->spinlock);
}
}
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state) {
sem->signaled = initial_state;
mutex_init(&sem->mutex);
}
void binary_semaphore_destroy(Binary_Semaphore *sem) {
mutex_destroy(&sem->mutex);
}
void binary_semaphore_wait(Binary_Semaphore *sem) {
mutex_acquire_or_wait(&sem->mutex);
while (!sem->signaled) {
mutex_release(&sem->mutex);
os_yield_thread();
mutex_acquire_or_wait(&sem->mutex);
}
sem->signaled = false;
mutex_release(&sem->mutex);
}
void binary_semaphore_signal(Binary_Semaphore *sem) {
mutex_acquire_or_wait(&sem->mutex);
sem->signaled = true;
mutex_release(&sem->mutex);
}
#endif