-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcs_spinlock.hpp
More file actions
89 lines (72 loc) · 2.19 KB
/
mcs_spinlock.hpp
File metadata and controls
89 lines (72 loc) · 2.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#pragma once
#include <atomic>
#include <thread/util/spin_wait.hpp>
namespace thread::sync {
class QueueSpinLock {
public:
class Guard {
friend class QueueSpinLock;
public:
explicit Guard(QueueSpinLock& host) : host_(host) {
host_.Acquire(this);
}
// Non-copyable
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
// Non-movable
Guard(Guard&&) = delete;
Guard& operator=(Guard&&) = delete;
~Guard() {
host_.Release(this);
}
private:
QueueSpinLock& host_;
std::atomic<Guard*> next_{nullptr};
std::atomic<bool> is_owner_{false};
};
private:
void Enqueue(Guard* waiter) {
// Phase 1: Acquire the tail
// synchronizes-with prior releases and future acquires of tail_
auto prev_tail = tail_.exchange(waiter, std::memory_order_acq_rel);
if (prev_tail == nullptr) {
waiter->is_owner_.store(true, std::memory_order_release);
return;
}
// Phase 2: Link the previous tail to the new waiter
prev_tail->next_.store(waiter, std::memory_order_release);
}
void Dequeue(Guard* waiter) {
auto old_waiter = waiter;
// synchronizes-with prior releases and future acquires of tail_
if (tail_.compare_exchange_strong(
/* expected */ old_waiter,
/* desired */ nullptr,
/* success */ std::memory_order_acq_rel,
/* failure */ std::memory_order_relaxed)) {
return;
}
// At this point we are sure that there are other waiters enqueued after us.
// It is possible that the next waiter has completed Phase 1
// but not Phase 2 yet, so we need to spin until it is done.
SpinUntilNextWaiter(waiter);
waiter->next_.load()->is_owner_.store(true, std::memory_order_release);
}
void SpinUntilNextWaiter(Guard* waiter) {
while (waiter->next_.load(std::memory_order_acquire) == nullptr) {
thread::util::SpinLoopHint();
}
}
void Acquire(Guard* waiter) {
Enqueue(waiter);
while (!waiter->is_owner_.load(std::memory_order_acquire)) {
thread::util::SpinLoopHint();
}
}
void Release(Guard* owner) {
Dequeue(owner);
}
private:
std::atomic<Guard*> tail_{nullptr};
};
} // namespace thread::sync