Счетчик атомной резьбы

Я экспериментирую с атомарными примитивами C ++ 11 для реализации атомарного «счетчик потоков«В некотором роде, у меня есть один критический раздел кода. В этом блоке кода любой поток может свободно читать из памяти. Однако иногда я хочу сделать операцию сброса или очистки, которая сбрасывает всю разделяемую память в инициализированное значение по умолчанию.

Это похоже на прекрасную возможность использовать блокировку чтения-записи. C ++ 11 не включает мьютексы чтения-записи из коробки, но, возможно, что-то более простое подойдет. Я думал, что эта проблема будет отличной возможностью познакомиться с атомарными примитивами C ++ 11.

Поэтому я некоторое время обдумывал эту проблему, и мне кажется, что все, что мне нужно сделать, это:

  1. Всякий раз, когда поток входит в критическую секцию, приращение
    переменная атомного счетчика

  2. Всякий раз, когда поток покидает критическую секцию, декремент
    переменная атомного счетчика

  3. Если нить желает сброс все
    переменные к значениям по умолчанию, он должен атомарно ждать счетчика
    чтобы быть 0, затем атомарно установить его в какое-то специальное значение «флага очистки», выполнить очистку, затем сбросить счетчик до 0.

  4. Конечно,
    потоки, желающие увеличить и уменьшить счетчик, должны также проверить наличие
    флаг очистки.

Итак, алгоритм, который я только что описал, может быть реализован с тремя функциями. Первая функция, increment_thread_counter() должен ВСЕГДА вызываться перед входом в критическую секцию. Вторая функция, decrement_thread_counter(), должен ВСЕГДА вызываться прямо перед выходом из критического раздела. Наконец, функция clear() можно вызвать извне критического раздела только если Счетчик потоков == 0.

Вот что я придумал:

Дано:

  1. Переменная счетчика потоков, std::atomic<std::size_t> thread_counter
  2. Постоянная 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 ™, а я не являюсь экспертом в алгоритмах без блокировки, я не совсем уверен, что этот алгоритм не зависит от состояния гонки.

Вопрос: Этот поток кода безопасен? Возможны ли здесь условия гонки?

4

Решение

У вас есть состояние гонки; плохие вещи случаются, если другой поток меняет счетчик между 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));
}
6

Другие решения


По вопросам рекламы [email protected]