c++synchronizationmutexatomichappens-before

Does an atomic acquire synchronize with mutex lock release?


I have an object that stores some settings in an unordered_map with string keys and variant values. As my library might be used from several threads and reads will very likely outnumber writes by a large margin, I've thought about a copy on write implementation where the "get" operation is lock free and the "put" operation is in a critical section, as in the example:

class Cfg {
    using M = unordered_map<string,X>;
    shared_ptr<const M> data;
    mutex write_lock;
public:
    X get(string key) {
        shared_ptr<const M> cur_ver = atomic_load_explicit(&data, memory_order_acquire);
        // Extract the value from the immutable *cur_ver
    }
    void put(string key, X value) {
        lock<muted> wlock(write_lock);
        // No need for the atomic load here because of the lock
        shared_ptr<const M> cur_ver = data;
        shared_ptr<const M> new_ver = ;// create new map with value included
        // QUESTION: do I need this store to be atomic? Is it even enough?
        atomic_store_explicit(&data, new_ver, memory_order_release);
    }
}

I am reasonably confident that the design works, as long as the acquire/release synchronization affects also the pointed-to data and not just the pointer value. However, my questions are as follows:


Solution

  • will the atomic acquire synchronize with the mutex unlock which is a "release" operation?

    No, in order for an acquire operation to synchronize-with a release operation, the acquire operation has to observe the changes of the release operation (or some change in a potential release sequence headed by that operation).

    So yes, you need the atomic store inside the lock. There is no guarantee that get will "see" the latest value from put since you only use acquire/release, so there is not total order between the store and load operations. If you want that guarantee you have to use memory_order_seq_cst.

    As a side-note - this implementation is most likely not lock-free, because in most library implementations atomic_load_explicit for shared_ptr is not lock-free. The problem is that you have to load the pointer and dereference that pointer to increment the ref-counter, in one atomic operation. This is not possible on most architectures, so atomic_load_explicit is usually implemented using a lock.