Library / SDK
DNedic/lockfree avatar
DNedic/lockfree

lockfree: header-only queues and buffers for C++11 that never allocate

A collection of lock-free data structures written in standard C++11

988 stars61 forksC++MIT

At a glance

What is it?
lockfree is an MIT licensed collection of lock-free data structures written in standard C++11, aimed at anything from a microcontroller to a high performance computing job. The single producer variants are the interesting ones, and the library draws its line at trivial types.
Who is it for?
lockfree fits embedded and signal handling work where a lock would be unacceptable, where allocation is not available, and where the producer and consumer roles are known at design time. The multi producer variants come with a hardware requirement, since they need atomic instructions that some low-end microcontrollers lack, and every structure is restricted to trivial types.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The library is split by producer and consumer count

The first decision the project makes for you is how many threads are involved, and the whole library is organised around that split. Under single-producer single-consumer structures you get a queue, a ring buffer, a bipartite buffer and a priority queue. Under multi-producer multi-consumer you get a queue and a priority queue.

The README is direct about the trade. The single producer structures are more performant and should generally be used whenever there is only one thread or interrupt pushing data and another one retrieving it. The multi producer structures are more general, supporting several producers and consumers at the same time, but they carry storage and performance overhead compared with the single producer ones, and they require atomic instructions that can be missing from some low-end microcontrollers.

That last sentence is a hard constraint rather than a caveat, and it is the main reason to prefer the single producer variants when you can. On a small microcontroller without hardware atomics, a multi producer queue either cannot be used at all or has to fall back on something that is not what you asked for.

The stated purpose of the whole collection is inter-process communication, and the project notes that lock-free structures often scale much better than lock-based ones as the number of operations and threads grows.

A two-method queue and the buffers built on it

The single producer queue is described as best for single element operations, extremely fast, with a simple API consisting of only two methods. Two methods is the entire surface: push something in, take something out. There is no resize, no iteration, no count query, and no way to look at the contents without consuming them.

The ring buffer is the more general structure, with the ability to handle multiple elements at a time, and it uses standard library copies, which is what makes it fast for bulk operations. The trade is in the API: a ring buffer needs a notion of a batch, so the caller is responsible for the size of the transfer, while the queue keeps the units of work at one element.

The bipartite buffer is a variation of the ring buffer with the ability to always provide linear space in the buffer, and that property is what enables in-buffer processing. A linear region means the consumer can operate on a contiguous span of the buffer rather than on a wrapped, fragmented one, which matters when the consumer is doing something like an FFT over a block of samples.

The priority queue, in both the single producer and the multi producer variants, adds the ability to provide different priorities for elements, and the documentation points at signals, events and communication packets as the cases it is for. The important part is that the priority is a property of the element, not of the arrival order, so a high priority item queued behind a long run of low priority ones still comes out first.

The two mistakes that cost the most: allocation and false sharing

The library lists its own design decisions, and two of them pay for themselves in every deployment.

The first is no dynamic allocation. Every structure keeps its data buffer as a class member, and since the data structures are array-based, the size is known at compile time when the type is templated. There is no allocator to configure, no failure path for a failed allocation, and no fragmentation in a long-running embedded system. The project calls out the compile-time known size and the header-only implementation as the reason for the performance claim.

The second is false sharing. The library uses additional padding to eliminate the phenomenon by default, where two logically separate variables share a cache line and every write to one invalidates the other's line in the other core's cache. The padding size is controlled by a compile-time constant:

cpp
LOCKFREE_CACHELINE_LENGTH

The cacheline length is 64 by default, which matches most x86 and Arm server parts. On embedded systems the project advises setting a second constant to false, because cache coherence is not something a microcontroller has:

cpp
LOCKFREE_CACHE_COHERENT

And there is a trap for anyone moving to recent Apple silicon, since the M1 and M2 have a cacheline length of 128 bytes, so the default of 64 is wrong there and the constant should be set accordingly.

The trivial type restriction and what it rules out

The known limitations section contains one sentence and it is the most important line in the README. All of the data structures in lockfree are only meant to be used for trivial types.

The consequence is worth spelling out, because it is easy to trip over at compile time rather than at run time. A trivial type is one the compiler can copy with a memcpy, with no user-defined copy constructor, no user-defined destructor and no virtual functions. That covers integers, floats, pointers, plain structs and standard library types that meet the same bar, and it excludes anything with a destructor, anything with a virtual method, and anything that owns memory. A std::string cannot travel through these structures, and a struct holding a std::vector cannot either.

The reason is the same property that makes the structures lock-free. Their correctness rests on the elements being moved between producer and consumer by a copy that cannot fail, cannot throw and cannot run user code, because there is no lock to hold while a constructor runs.

The README also gives the formal classification, which is precise and useful when you are choosing between libraries. All structures are bounded, array-based and lock-free, and the single producer ones are additionally wait-free and termination safe. Bounded is the word to hold on to: every structure has a fixed capacity you choose at compile time, and a full queue or a full buffer is a condition your code has to handle rather than an error the library grows around.

Why not just use a lock, or the RTOS queue

The project answers both of these questions, and the answers are about portability more than about speed.

On a hosted machine, the reasons to avoid locking are the classic ones: deadlocks, priority inversion and nondeterministic access latency, with a possible performance gain in some scenarios. The second point is the one that matters for library designers. Because the structures are header-only and templated, the same code can be compiled for POSIX and for non-POSIX environments without #ifdef branches or polymorphism, which removes a class of portability problem that locking code tends to accumulate.

On an embedded system the argument is different, because the project concedes that locking usually is not expensive there. The problem is that there is a wide variety of real-time operating systems with no standardised locking API, and because architectures range from 8051 to RISC-V, interrupt management methods are not standardised either. A lock-free structure sidesteps both problems, and the code can be compiled to run on any embedded platform supporting C++11, with the further benefit that it can be tested on a host machine without mocking.

For deeper background the README points to a talk series by Herb Sutter, a talk series on live lock-free or deadlock by Fedor Pikus, and Dmitry Vyukov's writing on lock-free algorithms. That last reference is the one to read first if you intend to modify any of the structures.

Three ways to get it, and the maintenance picture

There are three documented ways to obtain the library. The first is CMake, using the FetchContent mechanism, so the dependency is declared in your build rather than vendored by hand. The second is as a git submodule, the traditional approach for a header-only library. The third is downloading a release from GitHub.

The repository layout suits the third option well, since there is a CMakeLists.txt at the top level, a lockfree directory holding the headers, a tests directory, a docs directory with a separate page per structure, a .clang-format file, a .pre-commit-config.yaml and a CHANGELOG.md. The header-only nature means the usual question of a static library to rebuild does not arise, and the per-structure documentation pages are linked directly from the README.

On maintenance, the project is not archived and the last push was on 2026-10-09. The release history shows 2.0.10 in January 2025, then 3.0.0 in February 2026 and 3.0.1 in May 2026, so the major version moved recently and the minor and patch numbers since then are sparse. The changelog is in the repository and is where you should look to see what a major bump changed before you take the jump, since a version 3 in a collection of lock-free structures is a sign that some of the algorithms or the API behind them changed shape.

Editorial conclusion

lockfree fits embedded and signal handling work where a lock would be unacceptable, where allocation is not available, and where the producer and consumer roles are known at design time. The multi producer variants come with a hardware requirement, since they need atomic instructions that some low-end microcontrollers lack, and every structure is restricted to trivial types. Before you commit, check that your target has the atomic instructions you need, set LOCKFREE_CACHELINE_LENGTH to match the hardware, for instance 128 on Apple M1 and M2, and confirm that the types you plan to move through the queue are trivially copyable.

Frequently asked questions

What types can I push through the lockfree queues?

Only trivial types. The documented limitation states that all of the data structures in lockfree are only meant to be used for trivial types, which excludes anything with a user-defined destructor or copy constructor, and so excludes standard library types that own resources.

How do I set the cache line length for Apple silicon?

Set the LOCKFREE_CACHELINE_LENGTH compile time constant to 128 in that case, since the Apple M1 and M2 CPUs have a cache line length of 128 bytes while the default in the library is 64. On embedded systems the project also advises setting LOCKFREE_CACHE_COHERENT to false.

Do the multi-producer structures work on every microcontroller?

Not necessarily. The multi-producer multi-consumer structures require atomic instructions, and the documentation notes that these can be missing from some low-end microcontrollers. The single-producer single-consumer structures are the ones to prefer on such hardware.

How do I add lockfree to a CMake project?

The library is obtained through CMake FetchContent, as a git submodule, or by downloading a release from GitHub. FetchContent is the option that keeps the dependency declared in your build system rather than vendored by hand.

Official sources

  1. DNedic/lockfree on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dnedic-lockfree.svg)](https://hysenlabs.com/projects/dnedic-lockfree)

Was this useful?