Skip to content

Commit 78e14d3

Browse files
committed
New lock draft
1 parent 4f98026 commit 78e14d3

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#ifndef __NBL_DEMOTE_PROMOTE_WRITER_READERS_LOCK_H_INCLUDED__
2+
#define __NBL_DEMOTE_PROMOTE_WRITER_READERS_LOCK_H_INCLUDED__
3+
4+
#include <atomic>
5+
#include <thread>
6+
#include <mutex> // for std::adopt_lock_t
7+
8+
// TODO: Reset spins if have to yield?
9+
// TODO atomic_unsigned_lock_free? since C++20 (from SReadWriteSpinLock)
10+
11+
namespace nbl::system
12+
{
13+
14+
class demote_promote_writer_readers_lock
15+
{
16+
constexpr static inline uint32_t MaxActors = 1023;
17+
constexpr static inline uint32_t SpinsBeforeYield = 5000;
18+
struct StateSemantics
19+
{
20+
explicit inline operator uint32_t() const { return std::bit_cast<uint32_t>(*this); }
21+
22+
uint32_t currentReaders : 10 = 0;
23+
uint32_t pendingWriters : 10 = 0;
24+
uint32_t pendingUpgrades : 10 = 0;
25+
uint32_t writing : 1 = 0;
26+
uint32_t stateLocked : 1 = 0;
27+
};
28+
std::atomic_uint32_t state;
29+
30+
constexpr uint32_t flipLock = StateSemantics{ .currentReaders = 0,.pendingWriters = 0,.pendingUpgrades = 0,.writing = false,.stateLocked = true };
31+
constexpr uint32_t writingMask = StateSemantics{ .currentReaders = 0,.pendingWriters = 0,.pendingUpgrades = 0,.writing = true,.stateLocked = false };
32+
33+
/**
34+
* @brief Acquires lock for reading. This is the operation with the lowest priority, thread will be blocked until there are no writers and no readers waiting for a writer upgrade
35+
*/
36+
void read_lock()
37+
{
38+
constexpr uint32_t preemptedMask = StateSemantics{ .currentReaders = 0,.pendingWriters = MaxActors,.pendingUpgrades = MaxActors,.writing = true,.stateLocked = false };
39+
for (uint32_t spinCount = 0; true; spinCount++)
40+
{
41+
uint32_t actual = state.fetch_or(flipLock);
42+
// stateLocked: some other thread is doing work and expects to update state
43+
if (actual & flipLock)
44+
{
45+
if (spinCount > SpinsBeforeYield)
46+
std::this_thread::yield();
47+
continue;
48+
}
49+
// If there are any pending writers, upgrades or a thread is currently writing, cannot acquire lock (`preemptedMask`)
50+
// Must release flipLock and yield for other threads to progress
51+
if (actual & preemptedMask)
52+
{
53+
state.store(actual);
54+
if (spinCount > SpinsBeforeYield)
55+
std::this_thread::yield();
56+
continue;
57+
}
58+
actual += static_cast<uint32_t>(StateSemantics{ .currentReaders = 1 });
59+
// release the flipLock, increment currentReaders
60+
state.store(actual);
61+
reading = true;
62+
break;
63+
}
64+
}
65+
66+
/**
67+
* @brief Release lock after reading.
68+
*/
69+
void read_unlock()
70+
{
71+
for (uint32_t spinCount = 0; true; spinCount++)
72+
{
73+
uint32_t actual = state.fetch_or(flipLock);
74+
// stateLocked: some other thread is doing work and expects to update state
75+
if (actual & flipLock)
76+
{
77+
if (spinCount > SpinsBeforeYield)
78+
std::this_thread::yield();
79+
continue;
80+
}
81+
actual -= static_cast<uint32_t>(StateSemantics{ .currentReaders = 1 });
82+
// release the flipLock, decrement currentReaders
83+
state.store(actual);
84+
break;
85+
}
86+
}
87+
88+
/**
89+
* @brief Acquires lock for writing. Is superseded by reader upgrades: if any thread with a reader's lock is trying to upgrade, then this thread will be blocked until that reader becomes a writer
90+
*/
91+
void write_lock()
92+
{
93+
constexpr uint32_t preemptedMask = StateSemantics{ .currentReaders = MaxActors,.pendingWriters = 0,.pendingUpgrades = MaxActors,.writing = true,.stateLocked = false };
94+
bool registeredPending = false;
95+
for (uint32_t spinCount = 0; true; spinCount++)
96+
{
97+
uint32_t actual = state.fetch_or(flipLock);
98+
// stateLocked: some other thread is doing work and expects to update state
99+
if (actual & flipLock)
100+
{
101+
if (spinCount > SpinsBeforeYield)
102+
std::this_thread::yield();
103+
continue;
104+
}
105+
// If there are any pending upgrades or a thread is currently reading or writing, cannot acquire lock (`preemptedMask`), so release flipLock and yield for other threads to progress
106+
// Also registers this thread as a pending writer if not done so already
107+
if (actual & preemptedMask)
108+
{
109+
if (!registeredPending)
110+
{
111+
registeredPending = true;
112+
actual += static_cast<uint32_t>(StateSemantics{ .pendingWriters = 1 });
113+
}
114+
state.store(actual);
115+
if (spinCount > SpinsBeforeYield)
116+
std::this_thread::yield();
117+
continue;
118+
}
119+
if (registeredPending)
120+
actual -= static_cast<uint32_t>(StateSemantics{ .pendingWriters = 1 });
121+
// release the flipLock, declare that this thread will be writing
122+
actual |= writingMask;
123+
state.store(actual);
124+
break;
125+
}
126+
}
127+
128+
/**
129+
* @brief Releases lock for writing.
130+
*/
131+
void write_unlock()
132+
{
133+
for (uint32_t spinCount = 0; true; spinCount++)
134+
{
135+
uint32_t actual = state.fetch_or(flipLock);
136+
// stateLocked: some other thread is doing work and expects to update state
137+
if (actual & flipLock)
138+
{
139+
if (spinCount > SpinsBeforeYield)
140+
std::this_thread::yield();
141+
continue;
142+
}
143+
actual &= ~writingMask;
144+
// release the flipLock, decrement currentReaders
145+
state.store(actual);
146+
break;
147+
}
148+
}
149+
150+
/**
151+
* @brief Upgrades a thread with a reader's lock to a writer's lock. If a thread already has a reader's lock, use this instead of `read_unlock()` -> `write_lock()`
152+
*/
153+
void upgrade()
154+
{
155+
bool registeredPending = false;
156+
for (uint32_t spinCount = 0; true; spinCount++)
157+
{
158+
uint32_t actual = state.fetch_or(flipLock);
159+
// stateLocked: some other thread is doing work and expects to update state
160+
if (actual & flipLock)
161+
{
162+
if (spinCount > SpinsBeforeYield)
163+
std::this_thread::yield();
164+
continue;
165+
}
166+
// If no one's writing, take lock and release the flipLock
167+
if (!(actual & writingMask))
168+
{
169+
actual |= writingMask;
170+
// If had been registered earlier
171+
if (registeredPending)
172+
{
173+
actual -= static_cast<uint32_t>(StateSemantics{ .pendingUpgrades = 1 });
174+
}
175+
state.store(actual);
176+
break;
177+
}
178+
// Register pending if not done so earlier
179+
if (!registeredPending)
180+
{
181+
registeredPending = true;
182+
actual += static_cast<uint32_t>(StateSemantics{ .pendingUpgrades = 1 });
183+
}
184+
// Release flipLock
185+
state.store(actual);
186+
if (spinCount > SpinsBeforeYield)
187+
std::this_thread::yield();
188+
}
189+
}
190+
}
191+
192+
/**
193+
* @brief Downgrades a thread with a writer's lock to a reader's lock. If a thread already has a writer's lock, use this instead of `write_unlock()` -> `read_lock()`
194+
*/
195+
void downgrade()
196+
{
197+
for (uint32_t spinCount = 0; true; spinCount++)
198+
{
199+
uint32_t actual = state.fetch_or(flipLock);
200+
// stateLocked: some other thread is doing work and expects to update state
201+
if (actual & flipLock)
202+
{
203+
if (spinCount > SpinsBeforeYield)
204+
std::this_thread::yield();
205+
continue;
206+
}
207+
actual &= ~writingMask;
208+
actual += static_cast<uint32_t>(StateSemantics{ .currentReaders = 1 });
209+
// Release flipLock, declare that we're no longer writing and that we're reading
210+
state.store(actual);
211+
}
212+
}
213+
};
214+
215+
}
216+
217+
#endif

0 commit comments

Comments
 (0)