алгоритм — C ++ ручной мьютекс

Я сделал мьютекс ручной работы для своего проекта, но я сомневаюсь, что он безопасен для потоков …

    bool blocked;

while ( blocked )
{

}

blocked = true;
...
blocked = false;

Допустим, поток A проходит цикл while и не блокирует флаг вовремя (не успевает установить флаг в false) и нить B тоже проходит цикл while!

  1. Является ли это возможным? Зачем?

  2. Насколько я понимаю, мьютекс работает по тому же принципу. Почему это не может произойти в мьютексе? Я читал об атомных операциях, которые не могут быть прерваны … Так что check-if-mutex-available а также mutex-block не может быть прервано, верно?

3

Решение

Ваш код полностью перестал существовать!

Причина в том, что доступ к переменной blocked не атомно. Два потока могут читать его одновременно и решить, что мьютекс разблокирован, если два чтения происходят до того, как первый поток записывает true обновление и обновление распространяется на все процессоры.

Вам нужны атомарные переменные и атомный обмен, чтобы решить эту проблему. atomic_flag Тип именно то, что вы хотите:

#include <atomic>

std::atomic_flag blocked;

while (blocked.test_and_set()) { }  // spin while "true"
// critical work goes here

blocked.clear();                    // unlock

(В качестве альтернативы, вы можете использовать std::atomic<bool> а также exchange(true), но atomic_flag сделано специально для этого.)

Атомарные переменные не только мешают компилятору переупорядочивать код, который появляется быть несвязанным, если бы это был однопотоковый контекст, но они также заставляют компилятор генерировать необходимый код, чтобы не допустить переупорядочивания инструкций самим процессором таким образом, чтобы обеспечить непоследовательный поток выполнения.

Фактически, если вы хотите быть немного более эффективным, вы можете потребовать менее дорогого упорядочения памяти для операций set и clear, например:

while (blocked.test_and_set(std::memory_order_acquire)) { }  // lock

// ...

blocked.clear(std::memory_order_release);                    // unlock

Причина в том, что вы заботитесь только о правильном упорядочении в одном направлении: отложенное обновление в другом направлении не очень дорого, но требует последовательной согласованности (как по умолчанию) вполне может быть дорогим.


Важный: Приведенный выше код является так называемым спин-блокировка, потому что в то время как состояние заблокировано, мы заняты вращением ( while петля). Это очень плохо почти во всех ситуациях. Предоставленный ядром системный вызов mutex — это совсем другой источник рыбы, так как он позволяет потоку сигнализировать ядру, что он может перейти в спящий режим, и позволить ядру отменить планирование всего потока. Это почти всегда лучшее поведение.

8

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

  1. Это возможно, потому что процессор может переключаться с первого потока на второй после входа в цикл, но до блокировки мьютекса.
  2. Это возможно, потому что они используют операции на уровне ядра, чтобы гарантировать, что поток не переключается, пока не завершится определенная операция.

Например, в Windows вы можете сделать мьютекс похожим на этот.

3

Вы уже получили это.

  1. Да, это очень возможно. Для одного ядра потоки выполняются ОС через timeslicing. Он немного запускает поток A, затем приостанавливает его и немного запускает поток B. Поток A может быть приостановлен сразу после прохождения цикла while.

  2. Чтобы решить подобные проблемы, центральные процессоры внедрили специальные инструкции, которые НЕ МОГУТ быть прерваны ничем. Эти атомарные операции используются мьютексом для проверки флага и установки его за одну операцию.

1

Да, описанная вами ситуация может произойти. Причина в том, что поток может быть прерван между тестированием blocked является falseи настройка blocked в true, Чтобы получить желаемое поведение, вам нужно использовать или подражать атомному пробуй и набор операция.

Дополнительную информацию о тестировании и настройке можно найти Вот.

1

Реализация мьютекса должна обеспечивать взаимную исключительность (в этом смысл) и не получаться в вашем коде. Для его доступа требуется некоторая атомарная переменная и подходящий порядок памяти. В C ++ 11 лучше всего использовать std::mutex (в идеале вместе с std::lock), для C ++ 03 вы можете использовать boost::mutex и т.п.

1