我必须要有一个时刻,因为这应该很容易,但我似乎无法让它正常工作。
在GCC中实现原子计数器的正确方法是什么?
即。我想要一个从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到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;
}
实际上,我应该指出,每个线程获得不同的值并不是绝对关键的。如果两个线程碰巧同时读取相同的值,则不会出现问题。但它们在任何时候都不能超过限制。
答案 0 :(得分:13)
您的代码不是原子的(并且您的第二个get_count
甚至不会增加计数器值)!
开始时说count
为3,两个线程同时调用get_count
。其中一个将首先完成原子添加并将count
增加到4.如果第二个线程足够快,它可以在第一个线程将其重置为零之前将其递增到5
。
此外,在您的回绕处理中,您将count
重置为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 ++标准模型),很容易打破。我建议使用一个简单的互斥锁/锁,除非你对它进行了分析并发现它是一个瓶颈。
答案 1 :(得分:2)
你很幸运,因为你想要的范围恰好恰好适合2位。
简单的解决方案:让volatile变量永远计数。但在阅读之后,只使用最低的两位(val & 3
)。 Presto,0-3的原子计数器。
答案 2 :(得分:0)
即使使用volatile
,也无法在纯C中创建任何原子。你需要asm。 C1x将具有特殊的原子类型,但在那之前你会遇到asm。
答案 3 :(得分:0)
你有两个问题。
__sync_fetch_and_add
将返回 之前的 值(即在添加之前)。因此,在_count
变为3的步骤中,您的本地save_count
变量将返回2
。因此,您实际上必须先将_count
增加到4
,然后再将其作为3
返回。
但即使最重要的是,在将其重置为0之前,您还是专门寻找它>= 4
。如果您只是寻找它,那只是使用错误限制的问题高达三。