Girls just wanna have fast MPMC queues with bounded waiting
nahla.dev
nahla.dev
Classy disclaimer! matthieum's (long) reddit comment is also an informative read: https://www.reddit.com/r/rust/comments/1up0uhg/girls_just_wa...
It originally required double-width CAS, but IIRC in recent years someone figured out how to remove this to make it more portable
Best reference I could find from cursory google:
https://ppopp23.sigplan.org/details/PPoPP-2023-papers/2/The-...
There are simple node based algorithms that achieves a similar guarantee:
https://web.archive.org/web/20240928080729/https://www.1024c...
There is also a MPMC algorithm on this site very similar to the article
https://web.archive.org/web/20220524214823/https://www.1024c...
[1]: https://github.com/dorjoy03/dsync/blob/master/src/mpmc_queue...
You can't have a bounded queue that is always non-blocking because slow consumers can block producers.
You can't have a global FIFO order + multiple producers without slow producers blocking consumers.
You can't have a global FIFO order + have have non-atomic reserve and commit without a interrupted/de-scheduled producer thread being able to block the consumer
If you want atomic commit then you lose separate reserve which means either unbounded memory or atomic fixed-size data with sentinel values, ABA problems etc.
There are trade-offs everywhere, and it's best to pick the data structure that fits your needs just like any other problem.
That part I think is most crucial. Neither "Lock-free" nor "Wait-free" are vague terms for how awesome a thing is, they're specific properties which are expensive to provide, if you need such a property it was indispensable, if you don't need it then you can likely do better without it.
But I think Nathan Bronson's work out of IIRC Standford about 10 or 15 years ago is still more or less the canvas you paint on.
unsafe impl<T, const N: usize> Sync for WFQueue<T, N> {}
unsafe impl<T, const N: usize> Send for WFQueue<T, N> {}
These impls are unsound, because neither constrains `T` to be `Sync`/`Send`. As-written this would let you declare a `WFQueue<Rc<T>, N>` and pass non-atomic-refcount pointers between threads.The fix is straightforward:
unsafe impl<T: Sync, const N: usize> Sync for WFQueue<T, N> {}
unsafe impl<T: Send, const N: usize> Send for WFQueue<T, N> {}
I.e., WFQueue is only Sync if T is Sync, and likewise for Send.Actually, later on, the code makes a similar mistake, but only for one impl.
unsafe impl<'a, T, const N: usize> Send for DrivableWFEnqueue<'a, T, N> where T: Send {}
unsafe impl<'a, T, const N: usize> Send for DrivableWFDequeue<'a, T, N> {} unsafe impl<T: Send, const N: usize> Sync for WFQueue<T, N> {}
unsafe impl<T: Send, const N: usize> Send for WFQueue<T, N> {}I think your citation date is off, by the way. As far as I can tell, it was first published in January 2011.
If you align and pad each slot there won't be any false sharing and the stream prefetcher can kick in if there's only one producer or consumer.
If you use bijective hashing you reduce false sharing without aligning and padding. This can save memory at the expense of the stream prefetcher never kicking in.
Sounds like fun.