What's the best way to increment (or decrement) an AtomicCell with a range limit?

I'm using the crossbeam crate's AtomicCellfor a parameter which can range from 1-8 — but should never be 0 or 9. If I do a load, check, and then set, it's not Atomic — there's a small chance the value was changed inside my check. So I ended up with this:

        self.zoom.compare_and_swap(7,8);
        self.zoom.compare_and_swap(6,7);
        self.zoom.compare_and_swap(5,6);
        self.zoom.compare_and_swap(4,5);
        self.zoom.compare_and_swap(3,4);
        self.zoom.compare_and_swap(2,3);
        self.zoom.compare_and_swap(1,2);

…. which is a lot of compares and would get out of hand if the range were larger. Is there a better way to do this? Or should I use a Mutex or RwLock for this instead?

The typical solution to this would be to compare_and_swap in a loop.

fn incr_max(i: &AtomicU32, max: u32) -> u32 {
    loop {
        let value = i.load(Ordering::Relaxed);
        if value == max { return value; }
        
        if i.compare_and_swap(value, value + 1, Ordering::Relaxed) == value {
            return value;
        }
    }
}

playground

1 Like

That's basically the same as my unrolled version, right?

No, the loop will only trigger if someone else changed the atomic between the load and swap.

1 Like

Also note, since the original code didn't check the return value of compare_and_swap, it can fail if two threads try to increment the value:

// Supposed the value is 6 when the code begins running.
self.zoom.compare_and_swap(7,8); // This compare fails.
// Now another thread increments the value to 7
self.zoom.compare_and_swap(6,7); // so this compare fails...
self.zoom.compare_and_swap(5,6); // ...and so do all the others...
self.zoom.compare_and_swap(4,5);
self.zoom.compare_and_swap(3,4);
self.zoom.compare_and_swap(2,3);
self.zoom.compare_and_swap(1,2);
// ...so the value is still 7 here. 

The value started at 6 and ended at 7, even though the "increment" operation ran twice. Fixing this would again require a loop, to try the whole operation from the beginning if none of its parts succeeded.

4 Likes