Потребовалось реализовать потоково и переполнительно безопасный счетчик на std::atomic. Сделал вот так, как и многие в интернете:
template<typename T, T COUNT_MIN = std::numeric_limits<T>::min()
, T COUNT_MAX = std::numeric_limits<T>::max() >
T SafeIncrement(std::atomic<T> & counter)
{
static_assert(MIN < MAX, "range wrong");
T current = counter.load();
T result = current == COUNT_MAX ? COUNT_MIN : current + 1;
while(!counter.compare_exchange_strong(current, result))
{
result = current == COUNT_MAX ? COUNT_MIN : current + 1;
}
return result;
}
Но у меня возникло подозрение, не возникнет ли здесь гонок. При интенсивном почти непрерывном вызове SafeIncrement из параллельных потоков, не возможна ли ситуация когда отдельные потоки будут вечно (или надолго!) промахиваться с обменом пусть и атомарном?
Поясню о чем речь. Гонка была бы невозможна если бы потоки выполняли обмен строго экслюзивно и по-очередеи. Например, если они исполняются псевдопараллельно на одном вычислителе или такой механиз обеспечивает ОС. У нас же гарантируется только экслюзивность, а не очередность.
Так вот, на эту тему что-то предусмотрено в реализациях std::atomic? Или есть правильные решения?
Нет, такая ситуация невозможна. Потому что каждая ошибка обмена означает что какой-то другой поток успешно этот самый обмен совершил. Таким образом, на каждой итерации цикла уменьшается число конкурирующих потоков - а значит, рано или поздно они закончатся (если только по какой-то причине не прибывают быстрее чем убывают).
Однако, значительное замедление при одновременном доступе из разных потоков действительно возможно, поэтому таких переменных стоит избегать.
PS Ваше решение мне кажется каким-то переусложненным: можно же просто использовать беззнаковые целочисленные типы, у них переполнение не вызывает UB.
Сборка персонального компьютера от Artline: умный выбор для современных пользователей