std::atomic访问是否作为内存屏障?

7
编译器是否可以重新排列原子操作的指令,或者原子操作是否作为内存屏障?换句话说,在原子操作之后编写的指令是否可以在原子操作之前执行?
请看下面的代码。如果将useMapA = false移动到更新mapB之前并启动读线程,则会使用无效的mapB
注意:更新线程仅在每15分钟发生一次,因此我们有一个非常良好的结构流程,并且有一种避免调用昂贵的锁定调用的方法!
std::atomic<bool> useMapA;
std::map<string, string> mapA, mapB;

public void updateMap(map<string, string>* latestMap) {
    if (useMapA) {
        mapB = std::move(*latestMap);
        useMapA = false;
    } else {
        mapA = std::move(*latestMap);
        useMapA = true;
    }
}

inline map<string, string>& getMap() {
    return useMapA ? mapA : mapB;
}

编辑:我希望通过牺牲100%的线程安全来换取速度(时间=金钱)。这个读取函数需要运行得非常快。您可以假设15分钟足够长,以避免如果这个时间更短会导致竞争条件。


1
你是否阅读过有关原子操作的std::memory_order标准? - Macmade
1个回答

5
在回答您的问题之前,我想展示如何使用std::shared_ptr原子操作轻松实现该功能。以下实现方式高效且线程安全,而且读者无需创建地图的副本。
using string_map = std::map<std::string, std::string>;

std::shared_ptr<const string_map> map;

std::shared_ptr<const string_map> getMap()
{
    return std::atomic_load(&map);
}

void updateMap(string_map latestMap)
{
    std::shared_ptr<const string_map> temp{
        new string_map{std::move(latestMap)}};
    std::atomic_store(&map, temp);
}

现在,让我们看看你的代码。它有些复杂。为了使其更简单,假设updateMap每秒钟调用一次而不是每15分钟。 useMapA最初为true。更新线程执行以下语句,并在更新原子标志之前被中断:
if (useMapA) {
    mapB = std::move(*latestMap);

现在,读取线程只评估原子标志:
bool r1 = useMapA; // r1==true

更新线程将继续执行并将原子标志设置为false。一秒钟后,更新线程会评估原子标志:
if (useMapA) { // condition is false

现在,读取线程正在继续执行。两个线程都访问mapA,并且至少有一个线程对数据结构进行写入。这意味着存在数据竞争,也就是说,程序的行为是未定义的,无论是否真正发生了数据竞争。
如果updateMap每15分钟才被调用,会发生什么变化?除非在这15分钟内发生了其他同步操作,否则它仍然是一种数据竞争,因为C++标准不区分第二和第15分钟。

修改了原始帖子,通过返回一个map&而不是一个map来避免地图副本。你使用shared_ptrs的解决方案确实比原始实现更好,谢谢! - user221237

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接