Я сделал мьютекс ручной работы для своего проекта, но я сомневаюсь, что он безопасен для потоков …
bool blocked;
while ( blocked )
{
}
blocked = true;
...
blocked = false;
Допустим, поток A проходит цикл while и не блокирует флаг вовремя (не успевает установить флаг в false
) и нить B тоже проходит цикл while!
Является ли это возможным? Зачем?
Насколько я понимаю, мьютекс работает по тому же принципу. Почему это не может произойти в мьютексе? Я читал об атомных операциях, которые не могут быть прерваны … Так что check-if-mutex-available
а также mutex-block
не может быть прервано, верно?
Ваш код полностью перестал существовать!
Причина в том, что доступ к переменной 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 — это совсем другой источник рыбы, так как он позволяет потоку сигнализировать ядру, что он может перейти в спящий режим, и позволить ядру отменить планирование всего потока. Это почти всегда лучшее поведение.
Например, в Windows вы можете сделать мьютекс похожим на этот.
Вы уже получили это.
Да, это очень возможно. Для одного ядра потоки выполняются ОС через timeslicing. Он немного запускает поток A, затем приостанавливает его и немного запускает поток B. Поток A может быть приостановлен сразу после прохождения цикла while.
Чтобы решить подобные проблемы, центральные процессоры внедрили специальные инструкции, которые НЕ МОГУТ быть прерваны ничем. Эти атомарные операции используются мьютексом для проверки флага и установки его за одну операцию.
Да, описанная вами ситуация может произойти. Причина в том, что поток может быть прерван между тестированием blocked
является false
и настройка blocked
в true
, Чтобы получить желаемое поведение, вам нужно использовать или подражать атомному пробуй и набор операция.
Дополнительную информацию о тестировании и настройке можно найти Вот.
Реализация мьютекса должна обеспечивать взаимную исключительность (в этом смысл) и не получаться в вашем коде. Для его доступа требуется некоторая атомарная переменная и подходящий порядок памяти. В C ++ 11 лучше всего использовать std::mutex
(в идеале вместе с std::lock
), для C ++ 03 вы можете использовать boost::mutex
и т.п.