1. 15
  1.  

  2. 3

    I’ve spent a little bit more time to try it out for https://github.com/liuliu/dflat. Previously, I use https://github.com/glessard/swift-atomics. It sort of works, but has its own gotchas and also “at your own risk” because no SE-0282 to guarantee a memory model.

    Most importantly though, previous Swift Atomics implementation (and to a broader scope, the lightweight locks such as os_unfair_lock on iOS), you have to choose from two evils:

    1. Have a reference-counted object, thus, you have a stable memory address for either locking or atomic operations;
    2. Use struct to avoid reference-counting. But unlike C++, you don’t have move / copy constructor, so you don’t know if the underlying address was moved / copied to a different location. You have to be careful.

    The language itself spends a lot of time on balancing usability, memory safety and efficiency. That is why the whole language nowadays leaning very heavily on protocol + struct as the main programming paradigm (as demonstrated by SwiftUI) because it is safe (immutable by default, no object inheritance), easy to use (auto-synthesize Equatable, Hashable etc protocol conformance) and efficient (no reference counting).

    The previous implementations such as glessard/swift-atomics choose to use struct as the base, with a giant warning of “there will be dragons!” because for atomics, you really don’t want to allocate reference-counted memory for each of them separately. At the same time, the memory address can change under you. It is a dilemma.

    The new Swift Atomics library choose a different approach. To casually use it, you can use ManagedAtomic<> which will do the right thing, but you get a reference-counted memory for each and every of your “atomics”. This avoid the foot-gun situation for beginners.

    To seriously use the new Swift Atomics library, it requires more dances. Basically, you need to first allocate the storage somewhere:

    class MyOwnClassThatCanHoldTheAtomics {
        var atomic: UnsafeAtomic<Int>.Storage(0)
    }
    

    and each time to use it, everything has to be explicit:

    // Use withUnsafeMutablePointer to get a valid pointer until load call finishes.
    let loadedValue = withUnsafeMutablePointer(to: &atomic) {
        UnsafeAtomic(at: $0).load(ordering: .acquiring)
    }
    

    This is a lot of dances comparing to a ManagedAtomic:

    let atomic: ManagedAtomic<Int>(0)
    let loadedValue = atomic.load(ordering: .acquiring)
    

    It will be interesting to see how people tend to use it in the wild, but kudos to the Apple team put so much thoughts into the use-case and try to make the incorrect use as much as impossible.