Crate atomic_pingpong

source ·
Expand description

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.


  • A Buffer<T> consists of two copies of T plus one additional byte of state.
  • Smart pointer for reading from a Buffer<T>. Updates the buffer’s state when dropped.
  • Smart pointer for writing to a Buffer<T>. Updates the buffer’s state when dropped.