Skip to content

Commit 34c692b

Browse files
committed
add queue spinlock
1 parent 11eb58d commit 34c692b

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

src/components/sync/queue_spinlock.h

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// Created by konstantin on 28.07.24.
3+
//
4+
5+
#pragma once
6+
7+
#include <atomic>
8+
9+
namespace NSync {
10+
11+
class QueueSpinLock final {
12+
public:
13+
class Guard final {
14+
public:
15+
explicit Guard(QueueSpinLock& host) : host(host) {
16+
host.Acquire(this);
17+
}
18+
~Guard() {
19+
host.Release(this);
20+
}
21+
22+
void SetOwner() {
23+
is_owner.store(true);
24+
}
25+
26+
void SetNext(Guard* guard) {
27+
next.store(guard);
28+
}
29+
30+
bool IsOwner() const {
31+
return is_owner.load();
32+
}
33+
34+
bool HasNext() const {
35+
return next.load() != nullptr;
36+
}
37+
38+
void SetNextOwner() {
39+
next.load()->SetOwner();
40+
}
41+
42+
private:
43+
QueueSpinLock& host;
44+
std::atomic<Guard*> next{};
45+
std::atomic<bool> is_owner{};
46+
};
47+
private:
48+
void Acquire(Guard* guard) {
49+
auto ancestor = tail_.exchange(guard);
50+
if (ancestor == nullptr) {
51+
guard->SetOwner();
52+
return;
53+
}
54+
55+
ancestor->SetNext(guard);
56+
while(!guard->IsOwner()) {}
57+
}
58+
59+
void Release(Guard* guard) {
60+
if (guard->HasNext()) {
61+
guard->SetNextOwner();
62+
return;
63+
}
64+
65+
Guard* old_guard = guard;
66+
while(!tail_.compare_exchange_weak(old_guard, nullptr)) {
67+
if (guard->HasNext()) {
68+
guard->SetNextOwner();
69+
return;
70+
}
71+
old_guard = guard;
72+
}
73+
}
74+
75+
std::atomic<Guard*> tail_{};
76+
};
77+
78+
} // namespace NSync

test/components/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_source_files = [
55
'test_intrusive_list.cpp',
66
'test_ms_queue.cpp',
77
'test_async_mutex.cpp',
8+
'test_queue_spinlock.cpp',
89
]
910

1011
cpp = meson.get_compiler('cpp')
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//
2+
// Created by konstantin on 28.07.24.
3+
//
4+
5+
#include "gtest/gtest.h"
6+
7+
#include <thread>
8+
9+
#include <components/sync/queue_spinlock.h>
10+
11+
TEST(TestQueueSpinlock, LockUnlock) {
12+
NSync::QueueSpinLock spinlock;
13+
14+
{
15+
NSync::QueueSpinLock::Guard guard(spinlock); // <-- Acquired
16+
// Critical section
17+
} // <-- Released
18+
}
19+
20+
TEST(TestQueueSpinlock, SequentialLockUnlock) {
21+
NSync::QueueSpinLock spinlock;
22+
23+
{
24+
NSync::QueueSpinLock::Guard guard(spinlock);
25+
// Critical section
26+
}
27+
28+
{
29+
NSync::QueueSpinLock::Guard guard(spinlock);
30+
// Critical section
31+
}
32+
}
33+
34+
TEST(TestQueueSpinlock, ConcurrentIncrements) {
35+
NSync::QueueSpinLock spinlock;
36+
size_t counter = 0;
37+
38+
const size_t kIncrementsPerThread = 1000;
39+
40+
auto contender = [&] {
41+
for (size_t i = 0; i < kIncrementsPerThread; ++i) {
42+
NSync::QueueSpinLock::Guard guard(spinlock);
43+
44+
size_t current = counter;
45+
std::this_thread::yield();
46+
counter = current + 1;
47+
}
48+
};
49+
50+
std::thread t1(contender);
51+
std::thread t2(contender);
52+
t1.join();
53+
t2.join();
54+
55+
std::cout << "Shared counter value: " << counter << std::endl;
56+
57+
ASSERT_EQ(counter, 2 * kIncrementsPerThread);
58+
}

0 commit comments

Comments
 (0)