#buffer #producer-consumer #element #single-consumer #synchronization #size #ping-pong

atomic_pingpong

Lightweight no_std ping-pong buffer, using AtomicU8 for synchronization

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

#503 in Concurrency

MIT license

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.

No runtime deps