Originally published on Cohost.
Was talking with a friend, and he said something to the effect of “man, if only I had a lock-free ringbuffer right now…” I thought I had one of those lying around somewhere, but turns out I only sort of did:
- It was written in Rust, not C (hard to include in a random C project)
- It only supported pushes and pops of single elements (inefficient)
- It suffered from multiple logic bugs (atomic programming is hard!)
So! To prove to myself that I can still write C code in a pinch, I decided to knock one out real fast. And I did! https://github.com/p0lyw0lf/silly_ringbuffer took a bit longer than expected (setting up C environment on Windows, solving some hard bugs like ABA), but overall I am very satisfied with the results.
Included Features
- Pushes and pops of arbitrary sizes (up to size of ringbuffer)
- Threadsafe (safe to use without any external synchronization primitives)
- Lock-free (only uses atomic variables internally)
Wait-free (threads always make progress if they can, and return an error if they can’t)maybe not?
Missing Features
- No support for custom allocators (just uses
malloc
andfree
, tho this is the easiest to remedy) - No growable buffer (size must be known up-front)
- No “pop everything remaining” operation
- No synchronization-primitive-enabled variant (would be more efficient in cases of multiple readers or writers, currently there is just one part where we have to spin which is bad)
I might add these features sometime much later, but for now I consider it “good enough”; I have proven the point to myself, and my friend can continue with his own silly C project (he wanted a lock-free ringbuffer because “sharing a file across a fork()
” isn’t cross-platform enough, doesn’t work on Windows).
Also, despite being a hackathon-like project, I always write my code with lots of documentation, so go read it!!! you might learn something or find another bug idk