Multithreading 用户定义的原子数小于

Multithreading 用户定义的原子数小于,multithreading,openmp,stdatomic,Multithreading,Openmp,Stdatomic,我一直在阅读,似乎std::atomic不支持小于/大于变量的比较和交换 我正在使用OpenMP,需要安全地更新全局最小值。 我想这就像使用内置API一样简单。 但是,唉,所以我正试图提出我自己的实现 我主要关心的是,我不希望每次都使用omp关键部分来进行少于一次的比较,因为在大多数情况下,它可能会产生大量的同步开销,但收益很小 但是,在可能发现新的全局极小值的情况下(不太常见),同步开销是可以接受的。我想我可以用下面的方法实现它。希望有人提供建议 使用std::原子单元作为全局最小值 以原子方

我一直在阅读,似乎std::atomic不支持小于/大于变量的比较和交换

我正在使用OpenMP,需要安全地更新全局最小值。 我想这就像使用内置API一样简单。 但是,唉,所以我正试图提出我自己的实现

我主要关心的是,我不希望每次都使用omp关键部分来进行少于一次的比较,因为在大多数情况下,它可能会产生大量的同步开销,但收益很小

但是,在可能发现新的全局极小值的情况下(不太常见),同步开销是可以接受的。我想我可以用下面的方法实现它。希望有人提供建议

  • 使用std::原子单元作为全局最小值
  • 以原子方式将值读入线程本地堆栈
  • 将其与当前值进行比较,如果小于当前值,则尝试进入临界段
  • 一旦同步,验证原子值仍然小于新值,并相应地更新(关键部分的主体应该便宜,只需更新几个值)

  • 这是一个家庭作业,所以我试图让我自己的实现。请不要推荐各种库来实现这一点。但是,请对该操作可能产生的同步开销发表评论,或者如果它不好,请详细说明原因。谢谢。

    如果存在,您要查找的将被称为
    fetch\u min()
    :获取旧值并将内存中的值更新为
    min(当前,新)
    ,与
    fetch\u add
    完全相同,但使用
    min()

    x86上的硬件不直接支持此操作,但使用LL/SC的机器可以为其发出比使用
    CAS(旧,分钟(旧,新))
    retry循环模拟更高效的asm

    您可以使用CAS重试循环模拟任何原子操作。在实践中,它通常不需要重试,因为成功加载的CPU通常在计算加载结果后的几个周期内也会在CAS中成功,因此它是有效的

    有关使用CAS重试循环为
    原子
    创建
    fetch\u add
    的示例,请参见
    compare\u exchange\u weak
    double
    的普通。用
    min
    完成此操作,您就一切就绪


    回复:评论中的澄清:我想你是说你有一个全局最小值,但当你找到一个新的最小值时,你也想更新一些相关数据。你的问题令人困惑,因为“比较并交换小于/大于”对你没有帮助

    我建议使用
    atomic globmin
    来跟踪全局最小值,这样您就可以阅读它来决定是否进入临界区并更新与该最小值相关的状态

    仅在持有锁时(即在关键区域内)修改
    globmin
    。然后您可以更新它+相关数据。它必须是
    atomic
    ,这样只查看关键部分之外的
    globmin
    的读者就不会有数据竞争。查看相关额外数据的读卡器必须使用保护该数据的锁,并确保从遵守该锁的读卡器的角度来看,
    globmin
    +额外数据的更新“原子地”进行

    static std::atomic globmin;
    std::互斥全局锁;
    静态结构extradataglobmin_extra;
    void new_min_候选者(未签名的newmin、const struct Extradata和newdata)
    {
    //轻量提前检查,避免关键部位
    //只要globmin随时间单调递减,则无需订购
    if(newmin
    std::memory\u order\u relaxed
    对于globmin来说已经足够了:不需要对任何其他东西进行排序,只需要原子性。我们从critical section/lock获得关联数据的原子性/一致性,而不是从加载/存储
    globmin
    的内存排序语义

    这样,唯一的原子读修改写操作就是锁定本身。globmin上的所有内容都是加载或存储(更便宜)。多线程的主要成本仍然是缓存线的跳跃,但一旦拥有了缓存线,每个原子RMW的成本可能比现代x86()上的简单存储贵20倍


    在这种设计中,如果大多数候选对象不低于
    globmin
    ,缓存线将在大部分时间内保持不变,因此关键部分之外的
    globmin.load(std::memory\u order\u released)
    可以在L1D缓存中命中。它只是一个普通的加载指令,所以非常便宜。(在x86上,即使是seq cst加载也只是普通加载(发布加载只是普通存储,但seq_cst存储更昂贵)。在默认顺序较弱的其他体系结构上,seq_cst/acquire加载需要一个屏障。)

    您所寻找的将被称为
    fetch_min()
    如果存在:获取旧值并将内存中的值更新为
    min(当前、新)
    ,与
    fetch\u add
    完全相同,但具有