Added functionality to Spinlock
This commit is contained in:
+96
-19
@@ -120,10 +120,52 @@ spinlock_try_lock :: #force_inline proc "contextless" (lock: ^Spinlock) -> bool
|
||||
return lock_acquired
|
||||
}
|
||||
|
||||
// Spins until the lock is acquired, relaxing the CPU between attempts.
|
||||
spinlock_lock :: #force_inline proc "contextless" (lock: ^Spinlock) {
|
||||
for !spinlock_try_lock(lock) {
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_unlock :: #force_inline proc "contextless" (lock: ^Spinlock) {
|
||||
intrinsics.atomic_store_explicit(lock, false, .Release)
|
||||
}
|
||||
|
||||
// Spins until the lock is acquired, then unlocks at the end of the calling scope. Always returns
|
||||
// true so it can guard a critical section from within an `if`:
|
||||
//
|
||||
// if spinlock_guard(&lock) {
|
||||
// // critical section
|
||||
// }
|
||||
@(deferred_in = spinlock_unlock)
|
||||
spinlock_guard :: #force_inline proc "contextless" (lock: ^Spinlock) -> bool {
|
||||
spinlock_lock(lock)
|
||||
return true
|
||||
}
|
||||
|
||||
// Tries to acquire the lock once without spinning. Returns true and unlocks at the end of the
|
||||
// calling scope if acquired, otherwise returns false and does nothing:
|
||||
//
|
||||
// if spinlock_tryguard(&lock) {
|
||||
// // critical section, entered only if the lock was acquired
|
||||
// }
|
||||
@(deferred_in_out = spinlock_tryguard_unlock)
|
||||
spinlock_tryguard :: #force_inline proc "contextless" (lock: ^Spinlock) -> bool {
|
||||
return spinlock_try_lock(lock)
|
||||
}
|
||||
|
||||
// Deferred companion of `spinlock_tryguard`; unlocks only when the lock was actually acquired.
|
||||
@(private)
|
||||
spinlock_tryguard_unlock :: #force_inline proc "contextless" (lock: ^Spinlock, locked: bool) {
|
||||
if locked {
|
||||
spinlock_unlock(lock)
|
||||
}
|
||||
}
|
||||
|
||||
lock :: proc {
|
||||
spinlock_lock,
|
||||
}
|
||||
|
||||
try_lock :: proc {
|
||||
spinlock_try_lock,
|
||||
}
|
||||
@@ -132,6 +174,14 @@ unlock :: proc {
|
||||
spinlock_unlock,
|
||||
}
|
||||
|
||||
guard :: proc {
|
||||
spinlock_guard,
|
||||
}
|
||||
|
||||
tryguard :: proc {
|
||||
spinlock_tryguard,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
@@ -427,9 +477,12 @@ test_atomic_release_acquire_publish_visibility :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_spinlock_try_lock_mutual_exclusion :: proc(t: ^testing.T) {
|
||||
// Stress test for spinlock_try_lock: N threads spin-acquire the lock and
|
||||
// perform a deliberate non-atomic read-modify-write on shared data.
|
||||
test_spinlock_mutual_exclusion :: proc(t: ^testing.T) {
|
||||
// Stress test for every spinlock acquisition variant: N threads contend on a
|
||||
// single lock and perform a deliberate non-atomic read-modify-write on shared
|
||||
// data. Each iteration rotates through spinlock_try_lock, spinlock_lock,
|
||||
// spinlock_guard, and spinlock_tryguard so every variant runs concurrently and
|
||||
// must uphold mutual exclusion on the same lock.
|
||||
//
|
||||
// If mutual exclusion holds:
|
||||
// - `counter` ends at exactly NUM_THREADS * ITERATIONS_PER_THREAD
|
||||
@@ -461,21 +514,9 @@ test_spinlock_try_lock_mutual_exclusion :: proc(t: ^testing.T) {
|
||||
barrier: sync.Barrier
|
||||
sync.barrier_init(&barrier, NUM_THREADS)
|
||||
|
||||
thread_proc :: proc(th: ^thread.Thread) {
|
||||
ctx := cast(^Thread_Data)th.data
|
||||
s := ctx.shared
|
||||
|
||||
// All threads rendezvous here for maximum contention.
|
||||
sync.barrier_wait(ctx.barrier)
|
||||
|
||||
for _ in 0 ..< ITERATIONS_PER_THREAD {
|
||||
// Spin on try_lock until we acquire it.
|
||||
for !spinlock_try_lock(&s.lock) {
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
|
||||
// --- critical section start ---
|
||||
|
||||
// The single critical section every acquisition variant must protect. Sharing
|
||||
// it guarantees they all stress the exact same non-atomic read-modify-write.
|
||||
critical_section :: proc(s: ^Shared) {
|
||||
// Atomically bump the holder count so we can detect overlapping holders.
|
||||
holders := intrinsics.atomic_add_explicit(&s.concurrent_holders, 1, .Relaxed)
|
||||
|
||||
@@ -494,10 +535,46 @@ test_spinlock_try_lock_mutual_exclusion :: proc(t: ^testing.T) {
|
||||
s.counter = val + 1
|
||||
|
||||
intrinsics.atomic_sub_explicit(&s.concurrent_holders, 1, .Relaxed)
|
||||
}
|
||||
|
||||
// --- critical section end ---
|
||||
thread_proc :: proc(th: ^thread.Thread) {
|
||||
ctx := cast(^Thread_Data)th.data
|
||||
s := ctx.shared
|
||||
|
||||
// All threads rendezvous here for maximum contention.
|
||||
sync.barrier_wait(ctx.barrier)
|
||||
|
||||
for i in 0 ..< ITERATIONS_PER_THREAD {
|
||||
// Rotate through every acquisition variant so they all contend on the
|
||||
// same lock simultaneously and must each uphold mutual exclusion.
|
||||
switch i & 3 {
|
||||
case 0:
|
||||
// Manual spin on try_lock until we acquire it.
|
||||
for !spinlock_try_lock(&s.lock) {
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
critical_section(s)
|
||||
spinlock_unlock(&s.lock)
|
||||
case 1:
|
||||
// Blocking lock that loops internally until acquired.
|
||||
spinlock_lock(&s.lock)
|
||||
critical_section(s)
|
||||
spinlock_unlock(&s.lock)
|
||||
case 2:
|
||||
// Scoped guard: unlocks automatically at the end of the block.
|
||||
if spinlock_guard(&s.lock) {
|
||||
critical_section(s)
|
||||
}
|
||||
case 3:
|
||||
// Scoped try-guard: retry until acquired, auto-unlocks on success.
|
||||
for {
|
||||
if spinlock_tryguard(&s.lock) {
|
||||
critical_section(s)
|
||||
break
|
||||
}
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user