如何使用 fetch_sub 和 atomic_thread_fence 减少多线程?

Posted

技术标签:

【中文标题】如何使用 fetch_sub 和 atomic_thread_fence 减少多线程?【英文标题】:how to decrement mutithreaded using fetch_sub and atomic_thread_fence? 【发布时间】:2019-09-03 08:47:21 【问题描述】:

我们有一个成员方法 (bool try()),它应该是线程安全的,如果变量 m_count 大于 0,它会递减它,我们尽量避免互斥体,而是使用 fetch_sub 和 atomic_thread_fence。

struct Test 
  bool try() 
    if (m_count<1)
        return false;
    int count = m_count.fetch_sub(1, std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    return true;
  
  Test():m_count(1) 
private:
  std::atomic<int> m_count;

我们希望确保 m_count 永远不会小于 0,并且如果 m_count 减少,则 try 返回 true。以上两个线程可以将 m_count 从 1 递减到 -1,这是不可接受的。

【问题讨论】:

为什么先做轻松操作,再做内存栅栏?为什么不直接使用想要的内存顺序呢? 【参考方案1】:

加载if (m_count&lt;1) 和调用fetch_sub() 之间存在间隙。

假设m_count == 1,一个线程执行加载并继续,但在它执行fetch_sub()之前,第二个线程执行加载并获得相同的值(1)。现在两个线程都将执行fetch_sub() 并且m_count 变为-1

为了消除这种差距,您可以将比较和修改组合成一个原子比较和交换 (CAS) 操作,如下所示:

bool do_try() 
  bool modified=false;
  int current = m_count.load(std::memory_order_relaxed);
  do 
    if (current == 0)
      break;
    assert(current > 0);

   while (!(modified = m_count.compare_exchange_weak(current, current-1, std::memory_order_relaxed)));

  std::atomic_thread_fence(std::memory_order_acquire);
  return modified;

现在m_count 不能变成-1

如果compare_exchange() 返回false,它会将其第一个参数更新为最新值,因此您不必再次调用load()

【讨论】:

这可以在compare_exchange_weak 上使用memory_order_acquire 并完全删除atomic_thread_fence 吗? @MaximEgorushkin 是的,但如果你还必须在load 上使用它,那就太悲观了(在弱平台上可能会导致 2 个围栏指令) 为什么load 需要使用memory_order_acquire @MaximEgorushkin 这取决于调用它的代码是什么样的。

以上是关于如何使用 fetch_sub 和 atomic_thread_fence 减少多线程?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 原子减 atomic::fetch_sub fetch_add

Linux上的内存屏障和atomic_t

ARM下的原子操作实现原理

驱动同步互斥阻塞

原子性操作原理分析

字符驱动程序之——同步互斥阻塞