RavEngine
Loading...
Searching...
No Matches
semi_lockless_fifo.h
1/*
2Copyright 2018 Google Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS-IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17#ifndef RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_
18#define RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_
19
20#include <atomic>
21#include <chrono>
22#include <condition_variable>
23#include <mutex>
24#include <vector>
25
26#include "base/logging.h"
27
28namespace vraudio {
29
30// Thread-safe multiple producer - single consumer FIFO queue to share data
31// between threads. The FIFO takes over ownership of the queue elements. Note
32// that |PushBack| calls are synchronized with a mutex and may block. Calls to
33// |PopFront| are lockless and never block.
34//
35// @tparam DataType Object type that the FIFO handles.
36template <typename DataType>
38 public:
39 typedef std::chrono::steady_clock::duration ClockDuration;
40
42
44
45 // Takes over ownership of |input| and pushes it to the FIFO queue back.
46 //
47 // @param input Input element to be added to the FIFO queue.
48 void PushBack(DataType&& input);
49
50 // Pops element from FIFO queue front.
51 //
52 // @return Element from FIFO queue front. Must not be called if the queue is
53 // empty.
54 DataType PopFront();
55
56 // Returns true if FIFO queue is empty, false otherwise. This method is *not*
57 // thread-safe and should only be called from the consumer thread.
58 bool Empty() const;
59
60 // Clears the FIFO queue and deletes all its elements. This method is *not*
61 // thread-safe and should only be called from the consumer thread.
62 void Clear();
63
64 // Sleeps until the number of elements in the FIFO queue drop below a target
65 // threshold. This method can be used to synchronize the producer and the
66 // consumer. Sleeping is enabled by default and can be disabled via
67 // |EnableBlockingSleepUntilMethods|.
68 //
69 // @param target_size Target size of FIFO queue.
70 // @param max_wait Maximum waiting period.
71 // @return True if number of FIFO elements is below target size.
72 bool SleepUntilBelowSizeTarget(size_t target_size,
73 const ClockDuration& max_wait);
74
75 // Sleeps until the number of elements in the FIFO queue is greater or equal a
76 // target threshold. This method can be used to synchronize the producer and
77 // the consumer. Sleeping is enabled by default and can be disabled via
78 // |EnableBlockingSleepUntilMethods|.
79 //
80 // @param target_size Target size of FIFO queue.
81 // @param max_wait Maximum waiting period.
82 // @return True if number of FIFO elements is greater or equal the target
83 // size.
84 bool SleepUntilNumElementsInQueue(size_t target_size,
85 const ClockDuration& max_wait);
86
87 // Allows for unblocking |SleepUntil[BelowSizeTarget|NumElementsInQueue]|
88 // method.
89 void EnableBlockingSleepUntilMethods(bool enable);
90
91 private:
92 // Node in single-linked list.
93 struct Node {
94 Node() : next(nullptr) {}
95 std::atomic<Node*> next;
96 DataType data;
97 };
98
99 // Head of linked list.
100 Node* head_;
101
102 // Tail of linked list.
103 Node* tail_;
104
105 // Number of elements.
106 std::atomic<size_t> fifo_size_;
107
108 // Mutex to synchronize |PushBack| calls from multiple threads.
109 std::mutex push_mutex_;
110
111 // Conditional to signal consumption.
112 std::condition_variable pop_conditional_;
113
114 // Mutex to block on until signal consumption occurs.
115 std::mutex pop_conditional_mutex_;
116
117 // Conditional to signal new elements on the FIFO.
118 std::condition_variable push_conditional_;
119
120 // Mutex to block on until new elements have been added to the FIFO.
121 std::mutex push_conditional_mutex_;
122
123 // Flag to enable and disable blocking sleeping calls.
124 std::atomic<bool> enable_sleeping_;
125};
126
127template <typename DataType>
129 : fifo_size_(0), enable_sleeping_(true) {
130 head_ = tail_ = new Node();
131}
132
133template <typename DataType>
134SemiLocklessFifo<DataType>::~SemiLocklessFifo() {
135 Clear();
136 DCHECK_EQ(head_, tail_);
137 DCHECK(head_->next.load() == nullptr);
138 delete head_;
139}
140
141template <typename DataType>
142void SemiLocklessFifo<DataType>::PushBack(DataType&& input) {
143 std::lock_guard<std::mutex> lock(push_mutex_);
144 tail_->data = std::move(input);
145 Node* const new_node = new Node();
146 DCHECK(tail_->next.load() == nullptr);
147 tail_->next = new_node;
148 tail_ = new_node;
149 ++fifo_size_;
150
151 {
152 // Taking the lock and dropping it immediately assure that the notify
153 // cannot happen between the check of the predicate and wait of the
154 // |push_conditional_|.
155 std::lock_guard<std::mutex> lock(push_conditional_mutex_);
156 }
157 push_conditional_.notify_all();
158}
159
160template <typename DataType>
161DataType SemiLocklessFifo<DataType>::PopFront() {
162 DCHECK(!Empty());
163
164 Node* const front_node = head_;
165 head_ = front_node->next;
166
167 DataType output = std::move(front_node->data);
168 delete front_node;
169
170 DCHECK_GT(fifo_size_.load(), 0u);
171 --fifo_size_;
172
173 {
174 // Taking the lock and dropping it immediately assure that the notify
175 // cannot happen between the check of the predicate and wait of the
176 // |pop_conditional_|.
177 std::lock_guard<std::mutex> lock(pop_conditional_mutex_);
178 }
179 pop_conditional_.notify_one();
180 return output;
181}
182
183template <typename DataType>
184bool SemiLocklessFifo<DataType>::Empty() const {
185 return fifo_size_.load() == 0;
186}
187
188template <typename DataType>
189void SemiLocklessFifo<DataType>::Clear() {
190 while (!Empty()) {
191 PopFront();
192 }
193 DCHECK_EQ(fifo_size_, 0u);
194}
195
196template <typename DataType>
197bool SemiLocklessFifo<DataType>::SleepUntilBelowSizeTarget(
198 size_t target_size, const ClockDuration& max_wait) {
199 DCHECK_GT(target_size, 0);
200 std::unique_lock<std::mutex> lock(pop_conditional_mutex_);
201 pop_conditional_.wait_for(lock, max_wait, [this, target_size]() {
202 return fifo_size_ < target_size || !enable_sleeping_.load();
203 });
204 return fifo_size_ < target_size;
205}
206
207template <typename DataType>
208bool SemiLocklessFifo<DataType>::SleepUntilNumElementsInQueue(
209 size_t target_size, const ClockDuration& max_wait) {
210 DCHECK_GT(target_size, 0u);
211 std::unique_lock<std::mutex> lock(push_conditional_mutex_);
212 push_conditional_.wait_for(lock, max_wait, [this, target_size]() {
213 return fifo_size_ >= target_size || !enable_sleeping_.load();
214 });
215 return fifo_size_ >= target_size;
216}
217
218template <typename DataType>
219void SemiLocklessFifo<DataType>::EnableBlockingSleepUntilMethods(bool enable) {
220 enable_sleeping_ = enable;
221 // Taking the lock and dropping it immediately assure that the notify
222 // cannot happen between the check of the predicate and wait of the
223 // |pop_conditional_| and |push_conditional_|.
224 { std::lock_guard<std::mutex> lock(pop_conditional_mutex_); }
225 { std::lock_guard<std::mutex> lock(push_conditional_mutex_); }
226 pop_conditional_.notify_one();
227 push_conditional_.notify_one();
228}
229
230} // namespace vraudio
231
232#endif // RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_
Definition node.h:47
Definition semi_lockless_fifo.h:37