Я экспериментирую с атомарными примитивами C ++ 11 для реализации атомарного «счетчик потоков«В некотором роде, у меня есть один критический раздел кода. В этом блоке кода любой поток может свободно читать из памяти. Однако иногда я хочу сделать операцию сброса или очистки, которая сбрасывает всю разделяемую память в инициализированное значение по умолчанию.
Это похоже на прекрасную возможность использовать блокировку чтения-записи. C ++ 11 не включает мьютексы чтения-записи из коробки, но, возможно, что-то более простое подойдет. Я думал, что эта проблема будет отличной возможностью познакомиться с атомарными примитивами C ++ 11.
Поэтому я некоторое время обдумывал эту проблему, и мне кажется, что все, что мне нужно сделать, это:
Всякий раз, когда поток входит в критическую секцию, приращение
переменная атомного счетчика
Всякий раз, когда поток покидает критическую секцию, декремент
переменная атомного счетчика
Если нить желает сброс все
переменные к значениям по умолчанию, он должен атомарно ждать счетчика
чтобы быть 0, затем атомарно установить его в какое-то специальное значение «флага очистки», выполнить очистку, затем сбросить счетчик до 0.
Конечно,
потоки, желающие увеличить и уменьшить счетчик, должны также проверить наличие
флаг очистки.
Итак, алгоритм, который я только что описал, может быть реализован с тремя функциями. Первая функция, increment_thread_counter()
должен ВСЕГДА вызываться перед входом в критическую секцию. Вторая функция, decrement_thread_counter()
, должен ВСЕГДА вызываться прямо перед выходом из критического раздела. Наконец, функция clear()
можно вызвать извне критического раздела только если Счетчик потоков == 0.
Вот что я придумал:
Дано:
std::atomic<std::size_t> thread_counter
clearing_flag
установлен в std::numeric_limits<std::size_t>::max()
…
void increment_thread_counter()
{
std::size_t expected = 0;
while (!std::atomic_compare_exchange_strong(&thread_counter, &expected, 1))
{
if (expected != clearing_flag)
{
thread_counter.fetch_add(1);
break;
}
expected = 0;
}
}
void decrement_thread_counter()
{
thread_counter.fetch_sub(1);
}
void clear()
{
std::size_t expected = 0;
while (!thread_counter.compare_exchange_strong(expected, clearing_flag)) expected = 0;
/* PERFORM WRITES WHICH WRITE TO ALL SHARED VARIABLES */
thread_counter.store(0);
}
Насколько я могу судить, это должно быть поточно-ориентированным. Обратите внимание, что decrement_thread_counter
функция не должна требовать ЛЮБОЙ логики синхронизации, потому что increment()
всегда вызывается раньше decrement()
, Итак, когда мы добираемся до decrement()
, thread_counter никогда не может быть равен 0 или clearing_flag
,
Несмотря на это, поскольку THREADING IS HARD ™, а я не являюсь экспертом в алгоритмах без блокировки, я не совсем уверен, что этот алгоритм не зависит от состояния гонки.
Вопрос: Этот поток кода безопасен? Возможны ли здесь условия гонки?
У вас есть состояние гонки; плохие вещи случаются, если другой поток меняет счетчик между increment_thread_counter()
тест для clearing_flag
и fetch_add
,
Я думаю, что этот классический цикл CAS должен работать лучше:
void increment_thread_counter()
{
std::size_t expected = 0;
std::size_t updated;
do {
if (expected == clearing_flag) { // don't want to succeed while clearing,
expected = 0; //take a chance that clearing completes before CMPEXC
}
updated = expected + 1;
// if (updated == clearing_flag) TOO MANY READERS!
} while (!std::atomic_compare_exchange_weak(&thread_counter, &expected, updated));
}