lockfree-async is a C++ header-only simple template library for lock-free inter-thread communication and sharing of resources.
The fundamental building block of this library is the MpmcPopAllLifoStack, a multiple-producer multiple-consumer
LIFO stack implementing the "IBM Freelist" (Treiber stack) algorithm with push and pop_all operations only. The
absence of a single-node pop makes it immune to the ABA problem. The library originally relied on Ross Bencina's
Queue World for this primitive; it now ships its own minimal
modern-C++ implementation.
The template class MpmcPopAllLifoStack<Node> is a lock-free MPMC LIFO stack. The Node type must expose a
Node* next member.
The template class Messenger<T> is a wrapper around MpmcPopAllLifoStack. It implements functionality to send and
receive data of type T between threads in a lock-free way, and to preallocate the resources to do so.
The template class RealtimeObject<T> owns an object of class T which can be shared between a non realtime thread and a
realtime thread, so that the non realtime thread can perform any blocking operation, such as creating the object, and
the realtime thread can use the object.
The template class AsyncObject manages asynchronous creation, modification, and destruction of several instances of an object, that can be
used from realtime threads, while being created, modified and destroyed by a timer thread.
Commit 3695325 (the doctest suite + audit-driven fixes + QueueWorld removal pass) made the following
backwards-incompatible API changes. Code written against the previous revision will need the migrations described
below.
getOnRealtimeThread() and receiveChangesOnRealtimeThread() previously returned Object*. They now return
Object const*, enforcing through the type system the long-standing rule that the realtime thread must not mutate
the shared Object (mutation would race with the next non-realtime-side change() call, which reads through
Object const&). getOnRealtimeThread() is also const-qualified.
Migration:
- If you only read the realtime-side object — no change needed.
- If you previously mutated it — restructure so any mutable, RT-only state lives next to the RT code (a member of
whatever owns the
RealtimeObject), not inside the sharedObject. The sharedObjectshould be treated as immutable from the RT thread's perspective.
MessageNode<T> no longer exposes the QueueWorld-style link array. The following members are gone:
enum { LINK_INDEX_1, PREV_LINK, LINK_COUNT }MessageNode* links_[2]MessageNode*& next()accessor methodMessageNode*& prev()accessor methodMessageNode* last()(was O(n))int count()(was O(n) and the source of a previous null-deref UB)
They are replaced by:
MessageNode* nextdirect member (replaceslinks_[0]/next())MessageNode* prevdirect member (replaceslinks_[1]/prev())
Migration: rewrite node->next() as node->next, node->prev() as node->prev. If you depended on last() or
count(), walk the chain inline (each is a 3-line loop) or compute the tail alongside the consumption walk
(e.g. via the new value returned by handleMessageStack, see below).
The lockfree/QueueWorld/ directory (and LICENSE-QUEUE-WORLD) have been deleted. Code that included the
QueueWorld headers directly will no longer compile:
#include "lockfree/QueueWorld/QwMpmcPopAllLifoStack.h"— goneQwMpmcPopAllLifoStack<NodePtrT, LINK_INDEX>— goneQwLinkTraits,QwConfig— gone
Migration: use lockfree::MpmcPopAllLifoStack<Node> from the new lockfree/MpmcPopAllLifoStack.hpp. The new stack
operates on a plain Node type that exposes a Node* next member (no link array, no link-index template
parameter, no trait specialisation). The algorithm and memory orderings are unchanged.
The lockfree::LifoStack<T> alias still exists and still exposes push, push_multiple, pop_all, empty with
the same semantics, but it now resolves to MpmcPopAllLifoStack<MessageNode<T>> instead of the QueueWorld
template. Code that used the alias through its documented operations is unaffected; code that relied on the
underlying type or template parameters needs updating.
The free function lockfree::handleMessageStack used to return void. It now returns MessageNode<T>* — the
tail of the chain (the original LIFO bottom, i.e. the first-sent node), or nullptr if head was nullptr.
Returning the tail lets callers recycle the consumed chain via the new O(1)
Messenger::recycle(head, tail) overload without re-walking it.
Migration: callers that ignored the return value are source-compatible (the result simply gets discarded).
Callers that used the function in a context requiring a void return (e.g. using F = decltype(...) aliases or
strict signature matching for higher-order code) will need to update their types.