0

我有一个对象,它unordered_map使用字符串键和变量值存储一些设置。由于我的库可能会被多个线程使用,并且读取的数量很可能会大大超过写入的数量,因此我考虑过一个写入时复制实现,其中“get”操作是无锁的,而“put”操作是关键的部分,如示例中所示:

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);
    }
}

只要获取/释放同步也会影响指向的数据而不仅仅是指针值,我有理由相信该设计是有效的。但是,我的问题如下:

  • 这个工作需要锁内的原子存储吗?
  • 或者原子获取是否会与作为“释放”操作的互斥锁同步?
4

2 回答 2

2

原子获取是否会与作为“释放”操作的互斥锁同步?

不,为了使获取操作与释放操作同步,获取操作必须观察释放操作的变化(或以该操作为首的潜在释放序列的某些变化)。

所以是的,你需要锁内的原子存储。由于您只使用获取/释放,因此无法保证get会“看到”最新值put,因此存储和加载操作之间没有总顺序。如果你想要那个保证,你必须使用memory_order_seq_cst.

作为旁注 - 这个实现很可能不是无锁的,因为在大多数库实现atomic_load_explicit中 forshared_ptr不是无锁的。问题是您必须在一个原子操作中加载指针并取消引用该指针以增加 ref-counter 。这在大多数架构上是不可能的,因此atomic_load_explicit通常使用锁来实现。

于 2020-06-09T09:00:48.163 回答
1

如果您希望您的get函数始终返回最新值,则它是必需的。您可能会在同一时钟时间内发生多次读取和写入。使用原子内存顺序可确保写入在读取之前的顺序。

如果混合使用非原子存储和原子加载,这是未定义的行为。这个线程也讨论了它。您可能有一个又一个的写入。如果您使用非原子指令,您可能会遇到数据竞争。

根据cppreference

memory_order_acquire

一旦释放线程中对内存的所有访问(对加载线程有可见的副作用)发生后,该操作就会发生。

memory_order_release

该操作被安排在消费或获取操作之前发生,作为对内存的其他访问的同步点,这些访问可能对加载线程有明显的副作用。

于 2020-06-09T00:04:16.130 回答