Ваш код не является атомарным (а ваш второй 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 / C ++ до C ++ 0x на самом деле не имеет четко определенной модели памяти), и его легко сломать.Я рекомендую использовать простой мьютекс / блокировку, если вы не профилировали его и не обнаружили, что это узкое место.