-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
barrier
181 lines (143 loc) · 6.61 KB
/
barrier
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
Copyright (C) 2018-2024 Geoffrey Daniels. https://gpdaniels.com/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef GTL_EXECUTION_BARRIER_HPP
#define GTL_EXECUTION_BARRIER_HPP
// Summary: Thread syncronisation barrier.
#ifndef NDEBUG
# if defined(_MSC_VER)
# define __builtin_trap() __debugbreak()
# endif
/// @brief A simple assert macro to break the program if the barrier is misused.
# define GTL_BARRIER_ASSERT(ASSERTION, MESSAGE) static_cast<void>((ASSERTION) || (__builtin_trap(), 0))
#else
/// @brief At release time the assert macro is implemented as a nop.
# define GTL_BARRIER_ASSERT(ASSERTION, MESSAGE) static_cast<void>(0)
#endif
#if defined(_MSC_VER)
# pragma warning(push, 0)
#endif
#include <condition_variable>
#include <mutex>
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
namespace gtl {
/// @brief The barrier class blocks a number of threads until they are syncronised.
class barrier final {
private:
/// @brief A lockable object used to block threads.
std::mutex mutex;
/// @brief A condition variable that is waited on to synchronise threads.
std::condition_variable all_present;
/// @brief The number of threads required to be waiting before the barrier is triggered.
unsigned long long int trigger_count;
/// @brief The number of threads currently waiting.
unsigned long long int waiting_count;
/// @brief The state of the barrier.
bool triggered;
public:
/// @brief Destructor asserts no threads are waiting on the barrier.
~barrier() {
GTL_BARRIER_ASSERT(this->waiting_count == 0, "Ensure that there are no waiting threads when the barrier is destructed.");
}
/// @brief Constructor sets the number of sync calls required before the barrier is triggered.
/// @param required_trigger_count The number of sync calls required before the barrier is triggered.
barrier(unsigned long long int required_trigger_count = 0)
: trigger_count(required_trigger_count)
, waiting_count(0)
, triggered(false) {
}
/// @brief Deleted copy constructor.
barrier(const barrier&) = delete;
/// @brief Deleted move constructor.
barrier(barrier&&) = delete;
/// @brief Deleted copy assignment.
barrier& operator=(const barrier&) = delete;
/// @brief Deleted move assignment.
barrier& operator=(barrier&&) = delete;
public:
/// @brief Set the number calls required before the barrier is triggered.
/// @param new_trigger_count The number of sync calls required before the barrier is triggered.
void set_trigger_count(unsigned long long int new_trigger_count = 0) {
// Lock the barrier mutex
std::unique_lock<std::mutex> unique_lock(this->mutex);
static_cast<void>(unique_lock);
// Update the trigger count.
this->trigger_count = new_trigger_count;
// If there are already enough threads waiting.
if (this->waiting_count >= this->trigger_count) {
// Trigger the barrier.
this->triggered = true;
// Unlock the lock so that the notification can be immediately used.
unique_lock.unlock();
// Notify a thread to continue, this will in turn notify another, et cetera.
this->all_present.notify_one();
}
}
/// @brief Get the number of sync calls required before the barrier is triggered.
/// @return The number of sync calls required before the barrier is triggered.
unsigned long long int get_trigger_count() const {
return this->trigger_count;
}
/// @brief Get the number calls made to sync so far.
/// @return The number of calls made to sync so far.
unsigned long long int get_waiting_count() const {
return this->waiting_count;
}
public:
/// @brief Triggers the barrier unblocking all current blocked sync calls.
void trigger() {
{
// Lock the barrier mutex
std::lock_guard<std::mutex> lock_guard(this->mutex);
static_cast<void>(lock_guard);
// Trigger the barrier.
this->triggered = true;
}
// Notify a thread to continue, this will in turn notify another, et cetera.
this->all_present.notify_one();
}
public:
/// @brief Blocks the caller until the barrier is triggered.
void sync() {
// Open a scope to contain the unique lock.
{
// Lock the barrier mutex
std::unique_lock<std::mutex> unique_lock(this->mutex);
static_cast<void>(unique_lock);
// Immediately return if the the trigger_count is zero.
if (this->trigger_count == 0) {
return;
}
// Increment the number of waiting threads and check if we have reached the trigger threshold.
if (++this->waiting_count >= this->trigger_count) {
this->triggered = true;
}
if (!this->triggered) {
// Block threads here until enough are waiting to satisfy the trigger_count.
this->all_present.wait(unique_lock, [this] {
// The predicate will keep the condition variable waiting until the barrier is triggered.
return this->triggered;
});
}
// Once waiting is over the each thread decrements the waiting count, when it reaches zero the barrer is primed.
this->triggered = (--this->waiting_count != 0);
}
// Once the mutex is unlocked a notification can be sent to the condition variable to unblock another thread.
this->all_present.notify_one();
}
};
}
#undef GTL_BARRIER_ASSERT
#endif // GTL_EXECUTION_BARRIER_HPP