简体   繁体   中英

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:

  • Is the atomic store within the lock required for this to work?
  • Or will the atomic acquire synchronize with the mutex unlock which is a "release" operation?

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.

It's required if you want your get function to always return the latest value. It can occur that you have multiple reads and write occurs in the same clock time. Using atomic memory order ensures the order that write is before read.

If you mix non-atomic stores and atomic loads, it is undefined behavior. This thread also discussed it. You potentially have one write after another write. You can have data race if you use non-atomic instruction.

According cppreference

memory_order_acquire

The operation is ordered to happen once all accesses to memory in the releasing thread (that have visible side effects on the loading thread) have happened.

memory_order_release

The operation is ordered to happen before a consume or acquire operation, serving as a synchronization point for other accesses to memory that may have visible side effects on the loading thread.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM