TripleBuffer.h
Go to the documentation of this file.
1// Modified by Raphael Grimm, 2017
2// changed interface / renamed functions
3//
4// Modified by Adrian Gierakowski, 2014
5//
6// added (const T&) constructor to enable custom buffer initialisation
7// added getReadRef and getWriteRef to enable by reference access to
8// objects contained in the buffer
9//
10// minore code cleanup
11//
12//
13// ORIGINAL COPYRIGHT NOTICE
14//==============================================================================
15// Name : TripleBuffer.hxx
16// Author : André Pacheco Neves
17// Version : 1.0 (27/01/13)
18// Copyright : Copyright (c) 2013, André Pacheco Neves
19// All rights reserved.
20
21/*
22Redistribution and use in source and binary forms, with or without
23modification, are permitted provided that the following conditions are met:
24- Redistributions of source code must retain the above copyright
25notice, this list of conditions and the following disclaimer.
26- Redistributions in binary form must reproduce the above copyright
27notice, this list of conditions and the following disclaimer in the
28documentation and/or other materials provided with the distribution.
29- Neither the name of the <organization> nor the
30names of its contributors may be used to endorse or promote products
31derived from this software without specific prior written permission.
32THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
33ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
34WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
35DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
36DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
37(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
39ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
41SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42*/
43
44// Description :
45// Template class for a TripleBuffer as a concurrency mechanism,
46// using atomic operations
47// Credits :
48// http://remis-thoughts.blogspot.pt/2012/01/triple-buffering-as-concurrency_30.html
49//==============================================================================
50
51#pragma once
52
53#include <atomic>
54#include <iostream>
55#include <type_traits>
56
57#include <SimoxUtility/threads/system_thread_id.h>
58
59namespace armarx
60{
61 /**
62 * @brief A simple triple buffer for lockfree comunication between a single writer and a single reader.
63 *
64 * If more than one reader/writer is required, you have to synchronize the access.
65 * Writers must not have: writes to the same data cell (except it is an atomic et al) and no writes are allowed during swapping of the buffer.
66 * Readers must not read when the buffer is swapped.
67 * If a writer only writes parts of the data structure, data loss could occure! (use a WriteBufferedTripleBuffer instead)
68 *
69 */
70 template <typename T>
72 {
73 public:
74#define DEBUG_MODE_PRINT \
75 if (debugMode.load()) \
76 std::cout << '[' << simox::system_thread_id() << "] [" << this << "] "
77
78 TripleBuffer(T&& initR, T&& initH, T&& initW)
79 {
80 buffer[getReadBufferIndex()] = std::move(initR);
81 buffer[getHiddenBufferIndex()] = std::move(initH);
82 buffer[getWriteBufferIndex()] = std::move(initW);
83 }
84
85 template <class... Ts>
86 TripleBuffer(const Ts&... ts) : buffer{T(ts...), T(ts...), T(ts...)}
87 {
88 }
89
90 /// @return the write buffer
91 T&
93 {
94 DEBUG_MODE_PRINT << "access write buffer\n";
96 }
97
98 /// @return the write buffer
99 const T&
101 {
102 DEBUG_MODE_PRINT << "access write buffer\n";
103 return buffer[getWriteBufferIndex()];
104 }
105
106 /// @return the current read buffer
107 const T&
109 {
110 DEBUG_MODE_PRINT << "access read buffer\n";
111 return buffer[getReadBufferIndex()];
112 }
113
114 /// @return the most up to date read buffer
115 const T&
117 {
119 return getReadBuffer();
120 }
121
122 /// @return the current read buffer (sometimes required for more complex initialization)
123 T&
125 {
126 DEBUG_MODE_PRINT << "access non const read buffer\n";
127 return buffer[getReadBufferIndex()];
128 }
129
130 /// @return the current read buffer (sometimes required for more complex initialization)
131 T&
133 {
134 DEBUG_MODE_PRINT << "access non const hidden buffer\n";
136 }
137
138 /**
139 * @brief Swaps in the hidden buffer if it contains new data.
140 * @return True if new data is available
141 */
142 bool
144 {
145 uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
146
147 if (!hasNewWrite(flagsNow))
148 {
149 DEBUG_MODE_PRINT << "update read buffer. no update available\n";
150 return false;
151 }
152
153 while (!flags.compare_exchange_weak(flagsNow,
154 flagSwapReadWithHidden(flagsNow),
155 std::memory_order_release,
156 std::memory_order_consume))
157 ;
158 DEBUG_MODE_PRINT << "update read buffer. got update\n";
159 return true;
160 }
161
162 void
164 {
165 DEBUG_MODE_PRINT << "commit write buffer.\n";
167 }
168
169 template <class U = T>
170 typename std::enable_if<std::is_copy_constructible<U>::value>::type
171 reinitAllBuffers(const T& init)
172 {
173 buffer[0] = init;
174 buffer[1] = init;
175 buffer[2] = init;
176 }
177
178 void
179 reinitAllBuffers(T&& writeBuff, T&& hiddenBuff, T&& readBuff)
180 {
181 buffer[getWriteBufferIndex()] = std::move(writeBuff);
182 buffer[getHiddenBufferIndex()] = std::move(hiddenBuff);
183 buffer[getReadBufferIndex()] = std::move(readBuff);
184 }
185
186 void
187 setDebugMode(bool mode)
188 {
189 std::cout << '[' << simox::system_thread_id() << "] [0x" << this
190 << "] set debug mode to " << mode << '\n';
191 debugMode = mode;
192 }
193
194#undef DEBUG_MODE_PRINT
195 protected:
196 // non-copyable behavior
197 TripleBuffer(const TripleBuffer&) = delete;
199
200 // /////////////////////////////////////////////////////////////////////////////// //
201 // get indices
202 uint_fast8_t
204 {
205 return (flags.load(std::memory_order_consume) & readBufferIndexMask) >>
207 }
208
209 uint_fast8_t
211 {
212 return (flags.load(std::memory_order_consume) & writeBufferIndexMask) >>
214 }
215
216 uint_fast8_t
218 {
219 return (flags.load(std::memory_order_consume) & hiddenBufferIndexMask) >>
221 }
222
223 // /////////////////////////////////////////////////////////////////////////////// //
224 //buffer operations
225 /// @brief Swap the write buffer with the hidden buffer
226 void
228 {
229 uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
230 while (!flags.compare_exchange_weak(flagsNow,
231 flagSwapWriteWithHidden(flagsNow),
232 std::memory_order_release,
233 std::memory_order_consume))
234 ;
235 }
236
237 // void swapReadAndHiddenBuffer(); is done by updateReadBuffer()
238
239 // /////////////////////////////////////////////////////////////////////////////// //
240 //flag opertions
241 // check if the newWrite bit is 1
242 bool
243 hasNewWrite(uint_fast8_t flags) const
244 {
245 return flags & dirtyBitMask;
246 }
247
248 /// swap read and hidden indexes
249 /**
250 * @brief swap read and hidden indexes of given flags (set dirty to 0)
251 * @param flags the current flags
252 * @return the new flags
253 */
254 static uint_fast8_t
256 {
257 return 0 //set dirty bit to 0
259 hiddenToReadShift) // hidden index now is read index
261 << hiddenToReadShift) // read index now is hidden index
262 | (flags & writeBufferIndexMask); // keep write index
263 }
264
265 /**
266 * @brief swap write and hidden indexes of given flags (set dirty to 1)
267 * @param flags the current flags
268 * @return the new flags
269 */
270 static uint_fast8_t
272 {
273 return dirtyBitMask //set dirty bit to 1
275 << hiddenToWriteShift) // hidden index now is write index
277 hiddenToWriteShift) // write index now is hidden index
278 | (flags & readBufferIndexMask); // keep read index
279 }
280
281 // /////////////////////////////////////////////////////////////////////////////// //
282 // constants
283 static const uint_fast8_t readBufferIndexShift = 0;
284 static const uint_fast8_t hiddenBufferIndexShift = 2;
285 static const uint_fast8_t writeBufferIndexShift = 4;
286 static const uint_fast8_t dirtyBitShift = 7;
287
289 static const uint_fast8_t hiddenToWriteShift =
291
292 static const uint_fast8_t readBufferIndexMask = 3 << readBufferIndexShift; //0b00000011;
293 static const uint_fast8_t hiddenBufferIndexMask = 3 << hiddenBufferIndexShift; //0b00001100;
294 static const uint_fast8_t writeBufferIndexMask = 3 << writeBufferIndexShift; //0b00110000;
295 static const uint_fast8_t dirtyBitMask = 1 << dirtyBitShift; //0b10000000;
296 // initially dirty = 0, write 0, hidden = 1, read = 2
297 static const uint_fast8_t initialFlags = 2 + (1 << 2); //0b00000110;
298
299 // /////////////////////////////////////////////////////////////////////////////// //
300 // data
301 mutable std::atomic_uint_fast8_t flags{initialFlags};
303 std::atomic_bool debugMode{false};
304 };
305
306 /**
307 * @see TripleBuffer
308 * @brief Same as the TripleBuffer, but partial writes of the data structure are ok.
309 * The write operation may be a bit slower and memory consumption may be 1/3 higher.
310 */
311 template <typename T>
313 {
314 public:
315 WriteBufferedTripleBuffer() : writeBuffer(T())
316 {
317 }
318
319 WriteBufferedTripleBuffer(const T& init) : tripleBuffer{init}, writeBuffer(init)
320 {
321 }
322
323 /// @return the write buffer
324 T&
326 {
327 return writeBuffer;
328 }
329
330 /// @return the write buffer
331 const T&
333 {
334 return writeBuffer;
335 }
336
337 /// @return the current read buffer
338 const T&
340 {
341 return tripleBuffer.getReadBuffer();
342 }
343
344 /// @return the most up to date read buffer
345 const T&
347 {
348 return tripleBuffer.getUpToDateReadBuffer();
349 }
350
351 /// @return the current read buffer (sometimes required for more complex initialization)
352 T&
354 {
355 return tripleBuffer._getNonConstReadBuffer();
356 }
357
358 /// @return the current read buffer (sometimes required for more complex initialization)
359 T&
361 {
362 return tripleBuffer._getNonConstHiddenBuffer();
363 }
364
365 /// @return the current read buffer (sometimes required for more complex initialization)
366 T&
368 {
369 return tripleBuffer.getReadBuffer();
370 }
371
372 /**
373 * @brief Swaps in the hidden buffer if it contains new data.
374 * @return True if new data is available
375 */
376 bool
378 {
379 return tripleBuffer.updateReadBuffer();
380 }
381
382 void
384 {
385 tripleBuffer.getWriteBuffer() = writeBuffer;
386 tripleBuffer.commitWrite();
387 }
388
389 void
390 reinitAllBuffers(const T& init)
391 {
392 writeBuffer = init;
393 tripleBuffer.reinitAllBuffers(init);
394 }
395
396 void
397 setDebugMode(bool mode)
398 {
399 tripleBuffer.setDebugMode(mode);
400 }
401
402 private:
403 TripleBuffer<T> tripleBuffer;
404 T writeBuffer;
405 };
406} // namespace armarx
A simple triple buffer for lockfree comunication between a single writer and a single reader.
uint_fast8_t getWriteBufferIndex() const
static const uint_fast8_t readBufferIndexShift
void swapWriteAndHiddenBuffer()
Swap the write buffer with the hidden buffer.
static const uint_fast8_t hiddenBufferIndexMask
std::atomic_bool debugMode
static const uint_fast8_t hiddenToWriteShift
static const uint_fast8_t writeBufferIndexShift
static const uint_fast8_t initialFlags
static const uint_fast8_t dirtyBitShift
const T & getWriteBuffer() const
const T & getReadBuffer() const
static const uint_fast8_t hiddenBufferIndexShift
const T & getUpToDateReadBuffer() const
static const uint_fast8_t writeBufferIndexMask
uint_fast8_t getReadBufferIndex() const
uint_fast8_t getHiddenBufferIndex() const
std::enable_if< std::is_copy_constructible< U >::value >::type reinitAllBuffers(const T &init)
bool updateReadBuffer() const
Swaps in the hidden buffer if it contains new data.
TripleBuffer(T &&initR, T &&initH, T &&initW)
bool hasNewWrite(uint_fast8_t flags) const
std::atomic_uint_fast8_t flags
static const uint_fast8_t hiddenToReadShift
TripleBuffer & operator=(const TripleBuffer &)=delete
static const uint_fast8_t dirtyBitMask
void reinitAllBuffers(T &&writeBuff, T &&hiddenBuff, T &&readBuff)
static uint_fast8_t flagSwapReadWithHidden(uint_fast8_t flags)
swap read and hidden indexes
TripleBuffer(const Ts &... ts)
static uint_fast8_t flagSwapWriteWithHidden(uint_fast8_t flags)
swap write and hidden indexes of given flags (set dirty to 1)
static const uint_fast8_t readBufferIndexMask
void setDebugMode(bool mode)
TripleBuffer(const TripleBuffer &)=delete
void reinitAllBuffers(const T &init)
const T & getUpToDateReadBuffer() const
bool updateReadBuffer() const
Swaps in the hidden buffer if it contains new data.
This file offers overloads of toIce() and fromIce() functions for STL container types.
#define DEBUG_MODE_PRINT