原子线程计数器

时间:2014-08-07 17:33:47

标签: c++ multithreading c++11 atomic lockless

我正在尝试使用C ++ 11原子基元来实现各种原子“ 线程计数器 ”。基本上,我有一个关键的代码部分。在此代码块中,任何线程都可以从内存中自由读取。但是,有时候,我想进行重置或清除操作,将所有共享内存重置为默认的初始化值。

这似乎是一个使用读写锁的好机会。 C ++ 11不包含开箱即用的读写互斥锁,但可能更简单。我认为这个问题是一个很好的机会,可以更熟悉C ++ 11原子基元。

所以我想了一会儿这个问题,在我看来,我所要做的就是:

  1. 每当线程进入临界区时,递增 a 原子计数器变量

  2. 每当线程离开临界区时,减少 原子计数器变量

  3. 如果某个帖子希​​望重置全部 变量为默认值,它必须原子等待计数器 为0,然后原子地将其设置为一些特殊的“清除标志” 值,执行清除,然后将计数器重置为0.

  4. 当然, 希望增加和减少计数器的线程也必须检查 清除旗帜。

  5. 因此,我刚才描述的算法可以用三个函数实现。在进入临界区之前,必须始终调用第一个函数increment_thread_counter()。在离开临界区之前,必须始终调用第二个函数decrement_thread_counter()。最后,只有iff 线程计数器== 0,才能从关键部分 之外调用函数clear()

    这就是我提出的:

    假设:

    1. 线程计数器变量std::atomic<std::size_t> thread_counter
    2. 常量clearing_flag设置为std::numeric_limits<std::size_t>::max()
    3. ...

      void increment_thread_counter()
      {
          std::size_t expected = 0;
          while (!std::atomic_compare_exchange_strong(&thread_counter, &expected, 1))
          {
              if (expected != clearing_flag)
              {
                  thread_counter.fetch_add(1);
                  break;
              }
              expected = 0;
          }
      }
      
      void decrement_thread_counter()
      {
          thread_counter.fetch_sub(1);
      }
      
      void clear()
      {
          std::size_t expected = 0;
          while (!thread_counter.compare_exchange_strong(expected, clearing_flag)) expected = 0;
      
          /* PERFORM WRITES WHICH WRITE TO ALL SHARED VARIABLES */
      
          thread_counter.store(0);
      }
      

      据我所知,这应该是线程安全的。请注意,decrement_thread_counter函数不应该要求任何同步逻辑,因为increment()之前总是调用decrement()。因此,当我们到达decrement()时,thread_counter永远不能等于0或clearing_flag

      无论如何,由于THREADING是HARD™,并且我不是无锁算法的专家,我不完全确定这个算法是无竞争条件的。

      问题:此代码线程是否安全?这里有没有竞争条件?

1 个答案:

答案 0 :(得分:6)

你有竞争条件;如果另一个线程更改了increment_thread_counter()clearing_flag的测试和fetch_add之间的计数器,则会发生错误。

我认为这个经典的CAS循环应该更好:

void increment_thread_counter()
{
    std::size_t expected = 0;
    std::size_t updated;
    do {
        if (expected == clearing_flag) {     // don't want to succeed while clearing, 
             expected = 0;      //take a chance that clearing completes before CMPEXC
        }

        updated = expected + 1;
        // if (updated == clearing_flag) TOO MANY READERS!
    } while (!std::atomic_compare_exchange_weak(&thread_counter, &expected, updated));
}