/** *Singleproducersingleconsumerlock-freeandwait-freeringbuffer. * *Thisdatastructureallowsproducingdatafromonethread,andconsumingit *onanotherthread,safelyandwithoutexplicitsynchronization.Ifusedon *twothreads,thisdatastructureusesatomicsforthreadsafety.Itis *possibletodisabletheuseofatomicsatcompiletimeandonlyusethisdata *structureononethread. * *Therolefortheproducerandtheconsumermustbeconstant,i.e.,the *producershouldalwaysbeononethreadandtheconsumershouldalwaysbeon *anotherthread. * *Somewordsabouttheinnerworkingsofthisclass: *-Capacityisfixed.Onlyoneallocationisperformed,intheconstructor. *Whenreadingandwriting,thereturnvalueofthemethodallowscheckingif *theringbufferisemptyorfull. *-Wealwayskeepthereadindexatleastoneelementaheadofthewrite *index,sowecandistinguishbetweenanemptyandafullringbuffer:an *emptyringbufferiswhenthewriteindexisatthesamepositionasthe *readindex.Afullbufferiswhenthewriteindexisexactlyoneposition *beforethereadindex. *-Wesynchronizeupdatestothereadindexafterhavingreadthedata,and *thewriteindexafterhavingwrittenthedata.Thismeansthattheeach *threadcanonlytouchaportionofthebufferthatisnottouchedbythe *otherthread. *-Callersareexpectedtoprovidebuffers.Whenwritingtothequeue, *elementsarecopiedintotheinternalstoragefromthebufferpassedin. *Whenreadingfromthequeue,theuserisexpectedtoprovideabuffer. *Becausethisisaringbuffer,datamightnotbecontiguousinmemory, *providinganexternalbuffertocopyintoisaneasywaytohavelinear *dataforfurtherprocessing.
*/ template <typename T> class ring_buffer_base {
public: /** *Constructorforaringbuffer. * *Thisperformsanallocation,butistheonlyallocationthatwillhappen *forthelifetimeofa`ring_buffer_base`. * *@paramcapacityThemaximumnumberofelementthisringbufferwillhold.
*/
ring_buffer_base(int capacity) /* One more element to distinguish from empty and full buffer. */
: capacity_(capacity + 1)
{
assert(storage_capacity() < std::numeric_limits<int>::max() / 2 && "buffer too large for the type of index used.");
assert(capacity_ > 0);
data_.reset(new T[storage_capacity()]); /* If this queue is using atomics, initializing those members as the last *actionintheconstructoractsasafullbarrier,andallowcapacity()to
* be thread-safe. */
write_index_ = 0;
read_index_ = 0;
} /** *Push`count`zeroordefaultconstructedelementsinthearray. * *Onlysafelycalledontheproducerthread. * *@paramcountThenumberofelementstoenqueue. *@returnThenumberofelementenqueued.
*/ int enqueue_default(int count) { return enqueue(nullptr, count); } /** *@briefPutanelementinthequeue * *Onlysafelycalledontheproducerthread. * *@paramelementTheelementtoputinthequeue. * *@return1iftheelementwasinserted,0otherwise.
*/ int enqueue(T & element) { return enqueue(&element, 1); } /** *Push`count`elementsintheringbuffer. * *Onlysafelycalledontheproducerthread. * *@paramelementsapointertoabuffercontainingatleast`count`elements. *If`elements`isnullptr,zeroordefaultconstructedelementsare *enqueued. *@paramcountThenumberofelementstoreadfrom`elements` *@returnThenumberofelementssuccessfullycopedfrom`elements`and *insertedintotheringbuffer.
*/ int enqueue(T * elements, int count)
{ #ifndef NDEBUG
assert_correct_thread(producer_id); #endif
int wr_idx = write_index_.load(std::memory_order_relaxed); int rd_idx = read_index_.load(std::memory_order_acquire);
if (full_internal(rd_idx, wr_idx)) { return0;
}
int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
/* First part, from the write index to the end of the array. */ int first_part = std::min(storage_capacity() - wr_idx, to_write); /* Second part, from the beginning of the array */ int second_part = to_write - first_part;
private: /** Return true if the ring buffer is empty. * *@paramread_indexthereadindextoconsider *@paramwrite_indexthewriteindextoconsider *@returntrueiftheringbufferisempty,falseotherwise.
**/ bool empty_internal(int read_index, int write_index) const
{ return write_index == read_index;
} /** Return true if the ring buffer is full. * *Thishappensifthewriteindexisexactlyoneelementbehindtheread *index. * *@paramread_indexthereadindextoconsider *@paramwrite_indexthewriteindextoconsider *@returntrueiftheringbufferisfull,falseotherwise.
**/ bool full_internal(int read_index, int write_index) const
{ return (write_index + 1) % storage_capacity() == read_index;
} /** *Returnthesizeofthestorage.Itisonemorethanthenumberofelements *thatcanbestoredinthebuffer. * *@returnthenumberofelementsthatcanbestoredinthebuffer.
*/ int storage_capacity() const { return capacity_; } /** *Returnsthenumberofelementsavailableforreading. * *@returnthenumberofavailableelementsforreading.
*/ int available_read_internal(int read_index, int write_index) const
{ if (write_index >= read_index) { return write_index - read_index;
} else { return write_index + storage_capacity() - read_index;
}
} /** *Returnsthenumberofemptyelements,availableforwriting. * *@returnthenumberofelementsthatcanbewrittenintothearray.
*/ int available_write_internal(int read_index, int write_index) const
{ /* We substract one element here to always keep at least one sample
* free in the buffer, to distinguish between full and empty array. */ int rv = read_index - write_index - 1; if (write_index >= read_index) {
rv += storage_capacity();
} return rv;
} /** *Incrementsanindex,wrappingitaroundthestorage. * *@paramindexareferencetotheindextoincrement. *@paramincrementthenumberbywhich`index`isincremented. *@returnthenewindex.
*/ int increment_index(int index, int increment) const
{
assert(increment >= 0); return (index + increment) % storage_capacity();
} /** *@briefThisallowscheckingthatenqueue(resp.dequeue)arealwayscalled *bytherightthread. * *@paramidtheidofthethreadthathascalledthecallingmethodfirst.
*/ #ifndef NDEBUG staticvoid assert_correct_thread(std::thread::id & id)
{ if (id == std::thread::id()) {
id = std::this_thread::get_id(); return;
}
assert(id == std::this_thread::get_id());
} #endif /** Index at which the oldest element is at, in samples. */
std::atomic<int> read_index_; /** Index at which to write new elements. `write_index` is always at
* least one element ahead of `read_index_`. */
std::atomic<int> write_index_; /** Maximum number of elements that can be stored in the ring buffer. */ constint capacity_; /** Data storage */
std::unique_ptr<T[]> data_; #ifndef NDEBUG /** The id of the only thread that is allowed to read from the queue. */ mutable std::thread::id consumer_id; /** The id of the only thread that is allowed to write from the queue. */ mutable std::thread::id producer_id; #endif
};
private: /** *@briefFramestosamplesconversion. * *@paramframesThenumberofframes. * *@returnAnumberofsamples.
*/ int frames_to_samples(int frames) const { return frames * channel_count; } /** *@briefSamplestoframesconversion. * *@paramsamplesThenumberofsamples. * *@returnAnumberofframes.
*/ int samples_to_frames(int samples) const { return samples / channel_count; } /** Number of channels of audio that will stream through this ring buffer. */ int channel_count; /** The underlying ring buffer that is used to store the data. */
ring_buffer_base<T> ring_buffer;
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.