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
|
/*!********************************************************************
Audacity: A Digital Audio Editor
@file MessageBuffer.h
Paul Licameli split from PlaybackSchedule.h
**********************************************************************/
#ifndef __AUDACITY_MESSAGE_BUFFER__
#define __AUDACITY_MESSAGE_BUFFER__
#include <atomic>
//! Communicate data atomically from one writer thread to one reader.
/*!
This is not a queue: it is not necessary for each write to be read.
Rather loss of a message is allowed: writer may overwrite.
Data must be default-constructible and reassignable.
*/
template<typename Data>
class MessageBuffer {
struct UpdateSlot {
Data mData;
std::atomic<bool> mBusy{ false };
};
NonInterfering<UpdateSlot> mSlots[2];
std::atomic<unsigned char> mLastWrittenSlot{ 0 };
public:
void Initialize();
//! Move data out (if available), or else copy it out
/*!
@tparam Result is constructible from Data&& and forwards of other arguments
*/
template<typename Result = Data, typename... ConstructorArgs>
Result Read(ConstructorArgs &&...args);
//! Reassign a slot by move or copy
template<typename Arg = Data&&> void Write( Arg &&arg );
};
template<typename Data>
void MessageBuffer<Data>::Initialize()
{
for (auto &slot : mSlots)
// Lock both slots first, maybe spinning a little
while ( slot.mBusy.exchange( true, std::memory_order_acquire ) )
{}
mSlots[0].mData = {};
mSlots[1].mData = {};
mLastWrittenSlot.store( 0, std::memory_order_relaxed );
for (auto &slot : mSlots)
slot.mBusy.exchange( false, std::memory_order_release );
}
template<typename Data>
template<typename Result, typename... ConstructorArgs>
Result MessageBuffer<Data>::Read(ConstructorArgs &&...args)
{
// Whichever slot was last written, prefer to read that.
auto idx = mLastWrittenSlot.load( std::memory_order_relaxed );
idx = 1 - idx;
bool wasBusy = false;
do {
// This loop is unlikely to execute twice, but it might because the
// producer thread is writing a slot.
idx = 1 - idx;
wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire );
} while ( wasBusy );
// Copy the slot
Result result(
std::move( mSlots[idx].mData ), std::forward<ConstructorArgs>(args)... );
mSlots[idx].mBusy.store( false, std::memory_order_release );
return result;
}
template<typename Data>
template<typename Arg>
void MessageBuffer<Data>::Write( Arg &&arg )
{
// Whichever slot was last written, prefer to write the other.
auto idx = mLastWrittenSlot.load( std::memory_order_relaxed );
bool wasBusy = false;
do {
// This loop is unlikely to execute twice, but it might because the
// consumer thread is reading a slot.
idx = 1 - idx;
wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire );
} while ( wasBusy );
mSlots[idx].mData = std::forward<Arg>(arg);
mLastWrittenSlot.store( idx, std::memory_order_relaxed );
mSlots[idx].mBusy.store( false, std::memory_order_release );
}
#endif
|