std::原子访问是否充当内存屏障

Does std::atomic access serve as a memory barrier?

本文关键字:内存 是否 访问 std      更新时间:2023-10-16

编译器是否可以对原子上的指令进行重新排序,或者原子是否充当内存屏障?再说一遍,在原子指令之后编写的指令能在原子指令之前执行吗?

请参阅以下代码。如果在mapB更新之前移动了useMapA = false,并且读取线程开始,我们将使用无效的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分钟足够长,以避免比赛条件,如果这个时间短得多。

在回答您的问题之前,我想展示一下如何使用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++标准在1秒和15分钟之间没有区别。