-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBlockingStack.hpp
More file actions
226 lines (200 loc) · 7.09 KB
/
Copy pathBlockingStack.hpp
File metadata and controls
226 lines (200 loc) · 7.09 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
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#pragma once
#include "IWaitingStack.hpp"
#include <stack>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <chrono>
/**
* @brief A thread-safe LIFO stack implementation of the IWaitingStack interface.
* * This class provides a synchronized wrapper around std::stack, supporting:
* - Thread-safe push and pop operations.
* - Blocking pops that wait for data availability.
* - Timed-out waiting operations.
* - Non-blocking status queries.
* * @tparam ElementType The type of elements stored in the stack.
*/
template<typename ElementType>
class BlockingStack : public IWaitingStack<ElementType> {
private:
/// Internal container for stack elements.
std::stack<ElementType> internalStack_;
/// Mutex protecting access to the internal container and size.
mutable std::mutex stackMutex_;
/// Condition variable to signal waiting threads when new data arrives.
std::condition_variable dataCondition_;
public:
/**
* @brief Default constructor.
*/
BlockingStack() = default;
/**
* @brief Copy constructor.
* Performs a thread-safe copy of the source stack.
* @param other The stack to copy from.
*/
BlockingStack(const BlockingStack& other) {
std::lock_guard<std::mutex> lock(other.stackMutex_);
internalStack_ = other.internalStack_;
}
/**
* @brief Assignment operator is deleted to prevent unsafe concurrent copying.
*/
BlockingStack& operator=(const BlockingStack&) = delete;
/**
* @brief Move constructor.
* Safely transfers ownership of the underlying stack.
* @param other The stack to move from.
*/
BlockingStack(BlockingStack&& other) noexcept {
std::lock_guard<std::mutex> lock(other.stackMutex_);
internalStack_ = std::move(other.internalStack_);
}
/**
* @brief Virtual destructor.
*/
~BlockingStack() override = default;
// --- IStack Implementation ---
/**
* @brief Pushes a copy of the value onto the stack.
* Signals one waiting thread upon insertion.
* @param value The value to be pushed.
*/
void push(const ElementType& value) override {
{
std::lock_guard<std::mutex> lock(stackMutex_);
internalStack_.push(value);
}
dataCondition_.notify_one();
}
/**
* @brief Pushes a value onto the stack using move semantics.
* Signals one waiting thread upon insertion.
* @param value The value to be moved and pushed.
*/
void push(ElementType&& value) override {
{
std::lock_guard<std::mutex> lock(stackMutex_);
internalStack_.push(std::move(value));
}
dataCondition_.notify_one();
}
/**
* @brief Removes and returns the top element.
* @return Shared pointer to the popped element.
* @throws std::out_of_range If the stack is empty.
*/
std::shared_ptr<ElementType> pop() override {
std::lock_guard<std::mutex> lock(stackMutex_);
if (internalStack_.empty()) {
throw std::out_of_range("BlockingStack::pop(): stack is empty");
}
auto res = std::make_shared<ElementType>(std::move(internalStack_.top()));
internalStack_.pop();
return res;
}
/**
* @brief Attempts to pop the top element without blocking.
* @param value Reference where the popped value will be stored.
* @return true if an element was popped, false if the stack was empty.
*/
bool tryPop(ElementType& value) override {
std::lock_guard<std::mutex> lock(stackMutex_);
if (internalStack_.empty()) {
return false;
}
value = std::move(internalStack_.top());
internalStack_.pop();
return true;
}
/**
* @brief Attempts to pop the top element without blocking.
* @return Shared pointer to the element, or nullptr if the stack is empty.
*/
std::shared_ptr<ElementType> tryPop() override {
std::lock_guard<std::mutex> lock(stackMutex_);
if (internalStack_.empty()) {
return nullptr;
}
auto res = std::make_shared<ElementType>(std::move(internalStack_.top()));
internalStack_.pop();
return res;
}
/**
* @brief Checks if the stack is empty.
* @return true if empty, false otherwise.
*/
bool isEmpty() const override {
std::lock_guard<std::mutex> lock(stackMutex_);
return internalStack_.empty();
}
/**
* @brief Returns the current number of elements in the stack.
* @return Current stack size.
*/
size_t getSize() const override {
std::lock_guard<std::mutex> lock(stackMutex_);
return internalStack_.size();
}
/**
* @brief Clears all elements from the stack.
*/
void clear() override {
std::lock_guard<std::mutex> lock(stackMutex_);
while (!internalStack_.empty()) {
internalStack_.pop();
}
}
// --- IWaitingStack Implementation ---
/**
* @brief Blocks until an element is available, then removes and stores it.
* @param value Reference to store the popped value.
*/
void waitAndPop(ElementType& value) override {
std::unique_lock<std::mutex> lock(stackMutex_);
dataCondition_.wait(lock, [this] { return !internalStack_.empty(); });
value = std::move(internalStack_.top());
internalStack_.pop();
}
/**
* @brief Blocks until an element is available, then removes and returns it.
* @return Shared pointer to the popped element.
*/
std::shared_ptr<ElementType> waitAndPop() override {
std::unique_lock<std::mutex> lock(stackMutex_);
dataCondition_.wait(lock, [this] { return !internalStack_.empty(); });
auto res = std::make_shared<ElementType>(std::move(internalStack_.top()));
internalStack_.pop();
return res;
}
/**
* @brief Waits for an element with a timeout.
* @param value Reference to store the popped value.
* @param timeout Maximum duration to wait.
* @return true if an element was popped before the timeout expired, false otherwise.
*/
bool waitAndPopFor(ElementType& value, const std::chrono::milliseconds& timeout) override {
std::unique_lock<std::mutex> lock(stackMutex_);
if (!dataCondition_.wait_for(lock, timeout, [this] { return !internalStack_.empty(); })) {
return false;
}
value = std::move(internalStack_.top());
internalStack_.pop();
return true;
}
/**
* @brief Pushes multiple elements onto the stack.
* Notifies all waiting threads after insertion.
* @param elements Initializer list of elements to insert.
*/
void push_multiple(std::initializer_list<ElementType> elements) {
{
std::lock_guard<std::mutex> lock(stackMutex_);
for (auto& elem : elements) {
// move-casts are necessary as elements in initializer_list are const
internalStack_.push(std::move(const_cast<ElementType&>(elem)));
}
}
dataCondition_.notify_all();
}
};