I found a phenomenon that every time I called compare_exchange_weak, it almost appeared in a circular form, and almost the same code logic had to be written every time, increasing the mental burden of the user. I have a new idea here, adding a new function: compare_exchange_loop, which greatly simplifies the use of the cas function. It's implementation example (Here, it is given by trait to facilitate users to test):
use std::sync::atomic::{AtomicI64, Ordering};
pub trait AtomicExt<T> {
fn compare_exchange_loop(&self, f: impl Fn(T) -> T) -> (T, T);
}
impl AtomicExt<i64> for AtomicI64 {
fn compare_exchange_loop(&self, f: impl Fn(i64) -> i64) -> (i64, i64) {
let mut old = self.load(Ordering::Relaxed);
let mut new = f(old);
loop {
match self.compare_exchange_weak(old, new, Ordering::AcqRel, Ordering::Relaxed) {
Ok(_) => break,
Err(val) => (old, new) = (val, f(val)),
}
}
(old, new)
}
}
For example, I need to obtain a unique timestamp with a microsecond accuracy:
// use compare_exchange_weak
fn get_unique_timestamp1() -> i64 {
static TIMESTAMP: AtomicI64 = AtomicI64::new(0);
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let mut old = TIMESTAMP.load(Ordering::Relaxed);
let mut new = ts.max(old + 1);
loop {
match TIMESTAMP.compare_exchange_weak(old, new, Ordering::AcqRel, Ordering::Relaxed) {
Ok(_) => break,
Err(val) => (old, new) = (val, ts.max(val + 1)),
}
}
new
}
// use compare_exchange_loop
fn get_unique_timestamp2() -> i64 {
static TIMESTAMP: AtomicI64 = AtomicI64::new(0);
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let (_, new) = TIMESTAMP.compare_exchange_loop(|old| ts.max(old + 1));
new
}