5 releases
0.2.3 | Jun 12, 2023 |
---|---|
0.2.2 | Apr 25, 2023 |
0.2.1 | Apr 20, 2023 |
0.2.0 | Apr 19, 2023 |
0.1.0 | Apr 17, 2023 |
#774 in Concurrency
47 downloads per month
20KB
234 lines
A ping-pong buffer is a two-element buffer which alows for simultaneous access by a single producer and a single consumer. One element is reserved for writing by the producer, and the other element is reserved for reading by the consumer. When writing and reading are finished, the roles of the two elements are swapped (i.e. the one which was written will be next to be read, and the one which was read will be next to be overwritten). This approach avoids the need for memory copies, which improves performance when the element size is large.
This ping-pong buffer implementation uses an AtomicU8 for synchronization between the producer and consumer, resulting in thread-safety and interrupt-safety with minimum overhead. This implementation supports no_std environments, but requires a target which supports atomic compare and swap.
lib.rs
:
Lightweight ping-pong buffer intended for no_std targets.
A ping-pong buffer is a two-element buffer which allows simultaneous access by a single producer and a single consumer. One element is reserved for writing by the producer, and the other element is reserved for reading by the consumer. When writing and reading are finished, the roles of the two elements are swapped (i.e. the one which was written will be next to be read, and the one which was read will be next to be overwritten). This approach avoids the need for memory copies, which improves performance when the element size is large.
The ping-pong buffer is specifically designed to allow simultaneous reading and writing. However, the roles of the two elements can only be safely swapped when neither reading or writing is in progress. It is the user's responsibility to ensure that the timing of reads and writes allows for this to happen. If reads and writes are interleaved such that one or the other is always in progress, then the roles of the buffer elements will never be able to swap, and the reader will continue to read an old value rather than the new values which are being written.
A reference for reading is acquired by calling Buffer<T>::read()
, and a
mutable reference for writing is acquired by calling Buffer<T>::write()
.
The types returned are smart pointers (Ref<T>
and RefMut<T>
,
respectively), which automatically update the state of the ping-pong buffer
when they are dropped. Attempting to acquire a second reference for reading
or writing will fail if the first reference of that type has not been dropped.
To opt out of automatic reference management, a set of unsafe access functions
are available: read_unchecked()
, write_unchecked()
, release_read()
, and
release_write()
. These functions provide reduced runtime overhead but, of
course, care is required to use them safely.
Ordinarily, calls to read()
and write()
are as permissive as possible:
read()
succeeds unless reading is already in progress, and write()
succeeds unless writing is already in progress. Thus, depending on the
timing of read()
and write()
calls, certain data which is written may
never be read, and other data which is written may be read multiple times.
(This is an important distinction between a ping-pong buffer and a FIFO
ring buffer.) Alternative behavior is possible using the read_once()
function, which only returns a Ref<T>
if it points to data which has not
yet been read, and the write_no_discard()
function, which only returns a
RefMut<T>
if the buffer does not currently contain unread data.
The memory footprint of a Buffer<T>
is two of T
plus one additional byte
(an AtomicU8
) which is used to synchronize access by the producer and
consumer. The runtime overhead from this implementation is less than about
twenty instructions to acquire or release a reference to the ping-pong
buffer (assuming function inlining is enabled). However, this crate can
only be used on targets which include atomic compare/swap in their
instruction sets.