Consider this snippet code of implementing a lock-free multi-producer single-consumer queue in Rust
struct Node<T> {
next: AtomicPtr<Node<T>>,
value: Option<T>,
}
impl<T> Node<T> {
unsafe fn new(v: Option<T>) -> *mut Node<T> {
Box::into_raw(box Node { next: AtomicPtr::new(ptr::null_mut()), value: v })
}
}
pub struct Queue<T> {
head: AtomicPtr<Node<T>>,
tail: UnsafeCell<*mut Node<T>>,
}
impl<T> Queue<T> {
pub fn push(&self, t: T) {
unsafe {
let n = Node::new(Some(t)); // #1
let prev = self.head.swap(n, Ordering::AcqRel); // #2
(*prev).next.store(n, Ordering::Release); // #3
}
}
pub fn pop(&self) -> PopResult<T> {
unsafe {
let tail = *self.tail.get();
let next = (*tail).next.load(Ordering::Acquire); // #4
if !next.is_null() {
*self.tail.get() = next;
assert!((*tail).value.is_none());
assert!((*next).value.is_some());
let ret = (*next).value.take().unwrap();
let _: Box<Node<T>> = Box::from_raw(tail);
return Data(ret);
}
if self.head.load(Ordering::Acquire) == tail { Empty } else { Inconsistent }
}
}
}
Since the Rust atomic object model is the same as that of C++, I will reference C++ standards about atomic operations.
First, look at #2
, since [atomics.order] p10 says:
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
This read-modify-write operation guarantees that the invocation of push
will change self.head
in order and only a single thread will modify the next
field of that object returned by swap
.
#3
is a release store operation while #4
is an acquire load operation, hence, once #4
read the pointer written at #3
, they will synchronize, such that the modification happened at #1
will happen-before those operations that read/write the object after #4
.
If I understand correctly, the happen-before between #1
and the part after #4
is established by the synchronization of #3
and #4
. The read-modify-write operation at #2
merely guarantees that every thread that executes push
has its individual “pre”, and they happen in the modification order.
So, I wonder can the Ordering::AcqRel
at #2
be replaced with Relaxed
?