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