Атомный счетчик в gcc
У меня должно быть мгновение, потому что это должно быть легко, но я не могу заставить его работать правильно.
Каков правильный способ применения атомного счетчика в GCC?
то есть. Мне нужен счетчик, который работает от нуля до 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 включительно, а затем около нуля.
Он должен идти от 0 до 3. Обычно я делаю счетчик с оператором мод, но я не
знайте, как сделать это безопасно.
Возможно, эта версия лучше. Вы можете видеть какие-либо проблемы с ним или предлагать
лучшее решение.
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;
}
Собственно, я должен указать, что не совсем важно, чтобы каждый поток получал другое значение. Если бы два потока имели одно и то же значение одновременно, это не было бы проблемой. Но они не могут превышать лимит в любое время.
Ответы
Ответ 1
Ваш код не является атомарным (а второй get_count
даже не увеличивает значение счетчика)!
Скажите count
- 3 в начале и два потока одновременно вызывают get_count
. Один из них получит его атомное добавление, сделанное первым, и увеличит count
до 4. Если второй поток будет достаточно быстрым, он может увеличить его до 5
до того, как первый поток сбросит его до нуля.
Кроме того, в вашей обработке wraparound вы reset 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));
Этот подход обобщает и более сложные последовательности и операции обновления.
Тем не менее, этот тип бесконтактной операции сложно понять, в какой-то степени полагается на поведение undefined (все текущие компиляторы получают это право, но ни один стандарт C/С++ до того, как С++ 0x на самом деле хорошо знает, определенную модель памяти) и легко сломается. Я рекомендую использовать простой мьютекс/замок, если вы не профилировали его и не обнаружили, что это узкое место.
Ответ 2
Вам повезло, потому что диапазон, который вы хотите, соответствует точно двум битам.
Простое решение: пусть волатильная переменная подсчитывается навсегда. Но после того, как вы его прочитали, используйте только самые младшие два бита (val & 3
). Presto, счетчик атомов от 0-3.
Ответ 3
Невозможно создать что-либо атомарное в чистом C, даже с volatile
. Вам нужно asm. C1x будет иметь специальные типы атомов, но до тех пор вы застряли в asm.
Ответ 4
У вас две проблемы.
__sync_fetch_and_add
вернет значение предыдущее (т.е. перед добавлением одного). Итак, на шаге, где _count
становится 3, ваша локальная переменная save_count
возвращается 2
. Поэтому вам действительно нужно увеличивать _count
до 4
, пока он не вернется как 3
.
Но даже в дополнение к этому, вы специально ищете, чтобы он был >= 4
, прежде чем вы вернетесь к нему reset. Это просто вопрос использования неправильного предела, если вы ищете его только для получите до трех.