Skip to content

Commit 06c6554

Browse files
authored
sync: add SpinLock (#24788)
1 parent 5cd799e commit 06c6554

3 files changed

Lines changed: 102 additions & 0 deletions

File tree

‎thirdparty/stdatomic/nix/atomic_cpp.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ using std::memory_order_consume;
120120
using std::memory_order_relaxed;
121121
using std::memory_order_release;
122122
using std::memory_order_seq_cst;
123+
124+
#define memory_order_relaxed std::memory_order_relaxed
125+
#define memory_order_consume std::memory_order_consume
126+
#define memory_order_acquire std::memory_order_acquire
127+
#define memory_order_release std::memory_order_release
128+
#define memory_order_acq_rel std::memory_order_acq_rel
129+
#define memory_order_seq_cst std::memory_order_seq_cst
130+
123131
#else /* <atomic> unavailable, possibly because this is C, not C++ */
124132
#include <sys/types.h>
125133
#include <stdbool.h>
@@ -266,6 +274,7 @@ typedef enum
266274
memory_order_acq_rel = __ATOMIC_ACQ_REL,
267275
memory_order_seq_cst = __ATOMIC_SEQ_CST
268276
} memory_order;
277+
269278
/*
270279
* 7.17.4 Fences.
271280
*/

‎vlib/sync/spinlock_test.v‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import sync
2+
3+
fn test_spinlock() {
4+
mut counter := 0
5+
mut s := sync.new_spin_lock()
6+
num_threads := 10
7+
mut wg := sync.new_waitgroup()
8+
wg.add(num_threads)
9+
10+
for _ in 0 .. num_threads {
11+
spawn fn (mut wg sync.WaitGroup, s &sync.SpinLock, counter_ref &int) {
12+
defer {
13+
s.unlock()
14+
wg.done()
15+
}
16+
s.lock()
17+
(*counter_ref)++
18+
}(mut wg, s, &counter)
19+
}
20+
wg.wait()
21+
assert counter == num_threads
22+
}

‎vlib/sync/sync.c.v‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module sync
22

3+
import time
4+
35
@[noreturn]
46
fn cpanic(res int) {
57
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
@@ -15,3 +17,72 @@ fn should_be_zero(res int) {
1517
cpanic(res)
1618
}
1719
}
20+
21+
// SpinLock is a mutual exclusion lock that busy-waits (spins) when locked.
22+
// When one thread holds the lock, any other thread attempting to acquire it
23+
// will loop repeatedly until the lock becomes available.
24+
pub struct SpinLock {
25+
mut:
26+
locked u8 // Lock state: 0 = unlocked, 1 = locked
27+
padding [63]u8 // Cache line padding (fills to 64 bytes total)
28+
}
29+
30+
// new_spin_lock creates and returns a new SpinLock instance initialized to unlocked state
31+
pub fn new_spin_lock() &SpinLock {
32+
mut the_lock := &SpinLock{
33+
locked: 0
34+
}
35+
// Ensure initialization visibility across threads
36+
C.atomic_thread_fence(C.memory_order_release)
37+
return the_lock
38+
}
39+
40+
// lock acquires the spin lock. If the lock is currently held by another thread,
41+
// this function will spin (busy-wait) until the lock becomes available.
42+
@[inline]
43+
pub fn (s &SpinLock) lock() {
44+
// Expected value starts as unlocked (0)
45+
mut expected := u8(0)
46+
mut spin_count := 0
47+
max_spins := 100
48+
base_delay := 100 // nanosecond
49+
max_delay := 10000 // nanoseconds (10μs)
50+
51+
// Busy-wait until lock is acquired
52+
for {
53+
// Attempt atomic compare-and-swap:
54+
// Succeeds if current value matches expected (0),
55+
// then swaps to locked (1)
56+
if C.atomic_compare_exchange_weak_byte(&s.locked, &expected, 1) {
57+
// Prevent critical section reordering
58+
C.atomic_thread_fence(C.memory_order_acquire)
59+
return
60+
}
61+
62+
spin_count++
63+
// Exponential backoff after max_spins
64+
if spin_count > max_spins {
65+
// Calculate delay with cap: 100ns to 10μs
66+
exponent := int_min(spin_count / max_spins, 10)
67+
delay := int_min(base_delay * (1 << exponent), max_delay)
68+
time.sleep(delay * time.nanosecond)
69+
} else {
70+
// Reduce power/bus contention during spinning
71+
C.cpu_relax()
72+
}
73+
74+
// Refresh lock state before next attempt
75+
expected = C.atomic_load_byte(&s.locked)
76+
}
77+
}
78+
79+
// unlock releases the spin lock, making it available to other threads.
80+
// IMPORTANT: Must only be called by the thread that currently holds the lock.
81+
@[inline]
82+
pub fn (s &SpinLock) unlock() {
83+
// Ensure critical section completes before release
84+
C.atomic_thread_fence(C.memory_order_release)
85+
86+
// Atomically reset to unlocked state
87+
C.atomic_store_byte(&s.locked, 0)
88+
}

0 commit comments

Comments
 (0)