Skip to content

Commit b7013c6

Browse files
authored
Merge pull request #4 from per-framework/feature/using-atomic-wait-to-bail-out-of-spinning
Using atomic_wait & atomic_notify_all to bail out of spinning
2 parents da465b5 + 4faa0f9 commit b7013c6

File tree

5 files changed

+43
-21
lines changed

5 files changed

+43
-21
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# [](#contents) [MCS.C++](#) [![Gitter](https://badges.gitter.im/per-framework/community.svg)](https://gitter.im/per-framework/community) [![Build Status](https://travis-ci.org/per-framework/mcs.cpp.svg?branch=v1)](https://travis-ci.org/per-framework/mcs.cpp) [![Code Coverage](https://img.shields.io/codecov/c/github/per-framework/mcs.cpp/v1.svg)](https://codecov.io/gh/per-framework/mcs.cpp/branch/v1)
22

3-
[MCS lock](http://web.mit.edu/6.173/www/currentsemester/readings/R06-scalable-synchronization-1991.pdf)
4-
implementation for C++.
3+
[MCS](http://web.mit.edu/6.173/www/currentsemester/readings/R06-scalable-synchronization-1991.pdf)
4+
based lock implementation for C++.
55

66
See [`synopsis.hpp`](provides/include/mcs_v1/synopsis.hpp) for the API.
77

provides/include/mcs_v1/mcs.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55

66
#include "dumpster_v1/finally.hpp"
77

8-
inline void mcs_v1::lock::acquire(holder &holder) {
9-
Private::acquire(*this, holder);
8+
inline void mcs_v1::lock::acquire(holder &holder, unsigned max_spin_count) {
9+
Private::acquire(*this, holder, max_spin_count);
1010
}
1111

1212
inline void mcs_v1::lock::release(holder &holder) {
1313
Private::release(*this, holder);
1414
}
1515

1616
template <class Action>
17-
std::invoke_result_t<Action> mcs_v1::lock::holding(Action &&action) {
17+
std::invoke_result_t<Action> mcs_v1::lock::holding(Action &&action,
18+
unsigned max_spin_count) {
1819
holder holder;
19-
acquire(holder);
20+
acquire(holder, max_spin_count);
2021
auto releaser = dumpster::finally([&]() { release(holder); });
2122
return action();
2223
}

provides/include/mcs_v1/private.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ class Private {
1515
class holder_t;
1616
class lock_t;
1717

18-
static void acquire(lock_t &lock, holder_t &holder);
18+
enum state_t : uint8_t { SPINNING, WAITING, RELEASED };
19+
20+
static void acquire(lock_t &lock, holder_t &holder, unsigned max_spin_count);
1921
static void release(lock_t &lock, holder_t &holder);
2022
};
2123

2224
} // namespace mcs_v1
2325

2426
class mcs_v1::Private::holder_t {
2527
friend class Private;
26-
volatile bool *locked;
28+
std::atomic<std::atomic<state_t> *> state;
2729
};
2830

2931
class mcs_v1::Private::lock_t {

provides/include/mcs_v1/synopsis.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,22 @@ struct lock : Private::lock_t {
3131
/// Locks are not CopyAssignable.
3232
lock &operator=(const lock &) = delete;
3333

34+
static constexpr unsigned default_max_spin_count = 1000;
35+
3436
/// Acquires the lock. The `holder` must be allocated by the caller, kept
3537
/// alive while the lock is held, and passed to `release` to release the lock.
3638
/// A unique holder per live acquire is required.
37-
void acquire(holder &holder);
39+
void acquire(holder &holder,
40+
unsigned max_spin_count = default_max_spin_count);
3841

3942
/// Releases the lock. The `holder` must be the same instance that was passed
4043
/// in a matching call to `acquire`.
4144
void release(holder &holder);
4245

4346
/// Invokes the action holding the lock.
44-
template <class Action> std::invoke_result_t<Action> holding(Action &&action);
47+
template <class Action>
48+
std::invoke_result_t<Action>
49+
holding(Action &&action, unsigned max_spin_count = default_max_spin_count);
4550
};
4651

4752
} // namespace mcs_v1

provides/library/mcs.cpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
#include "mcs_v1/mcs.hpp"
22

3+
#include "polyfill_v1/atomic.hpp"
4+
35
#include "intrinsics_v1/pause.hpp"
46

5-
void mcs_v1::Private::acquire(lock_t &lock, holder_t &holder) {
6-
holder.locked = nullptr;
7-
if (holder_t *predecessor = lock.tail.exchange(&holder)) {
8-
volatile bool locked = true;
9-
predecessor->locked = &locked;
10-
do {
7+
void mcs_v1::Private::acquire(lock_t &lock,
8+
holder_t &holder,
9+
unsigned max_spin_count) {
10+
holder.state.store(nullptr, std::memory_order_relaxed);
11+
if (holder_t *predecessor =
12+
lock.tail.exchange(&holder, std::memory_order_acquire)) {
13+
std::atomic<state_t> state(SPINNING);
14+
predecessor->state.store(&state, std::memory_order_release);
15+
while (max_spin_count) {
1116
intrinsics_v1::pause();
12-
} while (locked);
17+
if (SPINNING != state.load(std::memory_order_acquire))
18+
return;
19+
max_spin_count -= 1;
20+
}
21+
if (SPINNING == state.exchange(WAITING, std::memory_order_acq_rel))
22+
std::atomic_wait(&state, WAITING, std::memory_order_acquire);
1323
}
1424
}
1525

1626
void mcs_v1::Private::release(lock_t &lock, holder_t &holder) {
1727
holder_t *pholder = &holder;
18-
if (!holder.locked) {
19-
if (lock.tail.compare_exchange_strong(pholder, nullptr))
28+
std::atomic<state_t> *state = holder.state.load(std::memory_order_relaxed);
29+
if (!state) {
30+
if (lock.tail.compare_exchange_strong(
31+
pholder, nullptr, std::memory_order_release))
2032
return;
2133
do {
2234
intrinsics_v1::pause();
23-
} while (!holder.locked);
35+
state = holder.state.load(std::memory_order_relaxed);
36+
} while (!state);
2437
}
25-
*holder.locked = false;
38+
if (WAITING == state->exchange(RELEASED))
39+
std::atomic_notify_all(state);
2640
}

0 commit comments

Comments
 (0)