【问题标题】:Does std::atomic access serve as a memory barrier?std::atomic 访问是否用作内存屏障?
【发布时间】:2014-05-24 23:45:03
【问题描述】:

编译器可以对原子指令重新排序,还是原子用作内存屏障? 再说一遍,写在原子指令之后的指令能在原子指令之前执行吗?

请参阅以下代码。如果useMapA = falsemapB 更新之前被移动并且读取线程开始,我们将使用无效的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::memory_order 的关于原子操作的标准吗?

标签: c++ multithreading


【解决方案1】:

在回答您的问题之前,我想展示一下如何使用 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 分钟之间产生差异。

【讨论】:

  • 编辑了原始帖子,通过返回地图&而不是地图来避免地图复制。您使用 shared_ptrs 的解决方案确实比原来的实现要好,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-30
  • 2010-12-07
  • 2013-05-28
  • 1970-01-01
  • 2021-12-20
  • 2014-08-04
  • 2017-08-23
相关资源
最近更新 更多