代码之家  ›  专栏  ›  技术社区  ›  hookenz

gcc中的原子计数器

  •  11
  • hookenz  · 技术社区  · 14 年前

    我必须有一点时间,因为这应该很容易,但我似乎无法让它正常工作。

    在GCC中实现原子计数器的正确方法是什么?

    i、 我想要一个从0到4的计数器,并且是线程安全的。

    我在做这件事(这是进一步包装在一个类中,但不是在这里)

    static volatile int _count = 0;
    const int limit = 4;
    
    int get_count(){
      // Create a local copy of diskid
      int save_count = __sync_fetch_and_add(&_count, 1);
      if (save_count >= limit){
          __sync_fetch_and_and(&_count, 0); // Set it back to zero
      }
      return save_count;
    }
    

    但它是从1到1-4(包括1-4),然后大约为零。
    应该是0-3。通常我会和一个mod操作员做一个计数器,但我不会 知道如何安全地做到这一点。

    也许这个版本更好。你能看出它有什么问题吗 更好的解决方案。

    int get_count(){
       // Create a local copy of diskid
       int save_count = _count;
       if (save_count >= limit){
          __sync_fetch_and_and(&_count, 0); // Set it back to zero
          return 0;
       }
    
       return save_count;
     }
    

    实际上,我应该指出,每个线程获得不同的值并不是绝对重要的。如果两个线程碰巧同时读取相同的值,那就不成问题了。但任何时候都不能超过限额。

    4 回复  |  直到 14 年前
        1
  •  13
  •   Fabian Giesen    14 年前

    你的代码不是原子的 get_count 甚至不增加计数器值)!

    count 在开始时是3,两个线程同时调用 获取计数 . 其中一个先完成原子加法,然后递增 计数 到4。如果第二个线程足够快,它可以将其增量为 5 在第一个线程将其重置为零之前。

    另外,在你的环绕处理中,你重置了 计数 到0但不是 save_count . 这显然不是我们想要的。

    如果 limit 是2的幂。永远不要自己做减量,只要用

    return (unsigned) __sync_fetch_and_add(&count, 1) % (unsigned) limit;
    

    或者

    return __sync_fetch_and_add(&count, 1) & (limit - 1);
    

    每次调用只执行一个原子操作,既安全又非常便宜。对于一般限制,您仍然可以使用 % ,但如果计数器溢出,则会中断序列。您可以尝试使用64位值(如果您的平台支持64位原子),希望它永远不会溢出;但这是一个坏主意。正确的方法是使用原子比较交换操作。你这样做:

    int old_count, new_count;
    do {
      old_count = count;
      new_count = old_count + 1;
      if (new_count >= limit) new_count = 0; // or use %
    } while (!__sync_bool_compare_and_swap(&count, old_count, new_count));
    

    这种方法也适用于更复杂的序列和更新操作。

    这就是说,这种类型的无锁操作很难正确,在某种程度上依赖于未定义的行为(所有当前编译器都是正确的,但是在C++ 0x实际上有一个定义良好的内存模型之前没有C/C++标准),并且很容易被破解。我建议使用一个简单的互斥/锁,除非您分析了它并发现它是一个瓶颈。

        2
  •  2
  •   Ben Voigt    14 年前

    你很幸运,因为你想要的范围正好能容纳2位。

    简单的解决方法:让volatile变量永远递增。但是在你读了之后,用最下面的两位( val & 3 ). 普雷斯托,0-3的原子计数器。

        3
  •  0
  •   R.. GitHub STOP HELPING ICE    14 年前

    在纯C中创建任何原子都是不可能的,即使 volatile . 你需要高潮。C1x将有特殊的原子类型,但在那之前,你必须使用asm。

        4
  •  0
  •   VoteyDisciple    14 年前

    你有两个问题。

    __sync_fetch_and_add 将返回 以前的 值(即在添加一个之前)。所以在这一步 _count 变成3,你的本地人 save_count 变数又回来了 2 . 所以你必须增加 _计数 高达 4 在它回来之前 3 .

    但即便如此,你还是特别希望 >= 4 重新设置为0之前。如果你只想得到高达三倍的上限,那么这只是一个使用错误上限的问题。