-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathASDP_SpinFreeQueue.hpp
More file actions
135 lines (116 loc) · 4.26 KB
/
Copy pathASDP_SpinFreeQueue.hpp
File metadata and controls
135 lines (116 loc) · 4.26 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
/*
* Copyright (C) 2024: Arizona Board of Regents on Behalf of the University of Arizona
*/
#pragma once
/**
* @file ASDP_SpinFreeQueue.hpp
* @brief Apache Strap-Down Pilotage utility class to provide a thread-safe spin-free queue.
*
* @author ReliaSolve.
* @date March 15, 2024.
*/
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <atomic>
namespace asdp {
/// @brief A thread-safe spin-free queue.
template <typename T> class SpinFreeQueue {
public:
/// @brief Constructor.
SpinFreeQueue() : head(nullptr), tail(nullptr), nodes(0) { }
/// @brief Destructor.
~SpinFreeQueue() {
std::lock_guard<std::mutex> lk(mut);
while (head) {
Node* old_head = head;
head = old_head->next;
delete old_head;
nodes--;
}
head = tail = nullptr;
}
/// @brief Enqueues a data item in a thread-safe way.
void enqueue(T data) {
Node* new_node = new Node;
new_node->data = std::move(data);
new_node->next = nullptr;
{ // Hold the lock for as short a time as possible
std::lock_guard<std::mutex> lk(mut);
if (nodes == 0) {
head = new_node;
tail = new_node;
} else {
tail->next = new_node;
tail = new_node;
}
nodes++;
}
dcv.notify_one();
}
/// @brief Waits for the queue to be reduced to a specified size with a timeout.
/// @details This method is used to wait for the queue to be reduced to a specified size
/// without spin-waiting or blocking indefinitely. It can thus be used in a thread
/// that needs to wait for the queue to be reduced to a certain size but also needs to
/// perform other tasks or watch for a done flag.
/// @param size The size to wait for.
/// @param timeout The maximum time to wait.
/// @return True if the queue is reduced to the specified size, false if the timeout is reached.
bool awaitEmpty(size_t size, const std::chrono::microseconds& timeout) {
std::unique_lock<std::mutex> cvlk(mut);
if (!ecv.wait_for(cvlk, timeout, [&] { return nodes <= size; })) {
return false;
}
return true;
}
/// @brief Dequeues a data item from the head of the queue in a thread-safe way with a timeout.
/// @details This method dequeues a data item from the queue in a thread-safe way
/// without spin-waiting or blocking indefinitely. It can thus be used in a thread
/// that needs to dequeue data items but also needs to perform other tasks or watch
/// for a done flag.
/// @param value The data item that was dequeued (if any).
/// @param timeout The maximum time to wait for a data item to be available.
/// @return True if a data item is dequeued, false if the timeout is reached.
bool dequeue(T& value, const std::chrono::microseconds& timeout) {
Node* old_head;
{ // Hold the lock for as short a time as possible, moving other operations outside.
std::unique_lock<std::mutex> cvlk(mut);
if (!dcv.wait_for(cvlk, timeout, [&] { return nodes != 0; })) {
return false;
}
value = head->data;
old_head = head;
head = old_head->next;
if (head == nullptr) {
tail = head;
}
nodes--;
}
ecv.notify_all();
delete old_head;
return true;
}
/// @brief Returns the number of nodes in the queue.
size_t size() const {
return nodes;
}
private:
/// @brief A node in the queue with a pointer to the next node.
struct Node {
T data = { }; ///< The data in the node.
Node* next = nullptr; ///< The next node in the queue, nullptr for the end.
};
Node* head; ///< The head of the queue, nullptr if empty.
Node* tail; ///< The tail of the queue, nullptr if empty.
std::atomic<size_t> nodes;///< The number of nodes in the queue.
/// Condition variable and its mutex for dequeue to avoid spin-wait for getting data
std::condition_variable dcv;
/// Condition variable and its mutex for enqueue to avoid spin-wait for queue depletion
std::condition_variable ecv;
/// Mutex for adjusting the queue and for the condition variables
std::mutex mut;
};
/// @brief Test the SpinFreeQueue class (defined in ASDP_Core_API library).
/// @return An empty string if the test is successful, otherwise an error message.
std::string SpinFreeQueue_Test();
} // namespace asdp