Хотя мьютекс может использоваться для решения других проблем, основная причина, по которой они существуют, заключается в том, чтобы обеспечить взаимное исключение и тем самым решить так называемое состояние гонки. Когда два (или более) потока или процесса пытаются получить доступ к одной и той же переменной одновременно, у нас есть потенциал для состояния гонки. Рассмотрим следующий код
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
Внутренности этой функции выглядят так просто. Это только одно утверждение. Однако типичный эквивалент псевдо-ассемблера может быть следующим:
load i from memory into a register
add 1 to i
store i back into memory
Поскольку все эквивалентные инструкции на языке ассемблера требуются для выполнения операции увеличения на i, мы говорим, что увеличение i является неатмосовой операцией. Элементарная операция - это операция, которая может быть выполнена на оборудовании с гарантией того, что она не будет прервана после начала выполнения инструкции. Инкремент i состоит из цепочки из 3 атомарных инструкций. В параллельной системе, где несколько потоков вызывают функцию, проблемы возникают, когда поток читает или пишет в неправильное время. Представьте, что у нас одновременно работают два потока, и один вызывает функцию сразу после другого. Предположим также, что мы инициализировали значение 0. Также предположим, что у нас много регистров и что два потока используют совершенно разные регистры, поэтому коллизий не будет. Фактическое время этих событий может быть:
thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1
То, что произошло, это то, что у нас есть два потока, которые увеличиваются одновременно, наша функция вызывается дважды, но результат не согласуется с этим фактом. Похоже, функция была вызвана только один раз. Это связано с тем, что атомарность «нарушена» на уровне машины, что означает, что потоки могут прерывать друг друга или работать вместе в неподходящее время.
Нам нужен механизм, чтобы решить это. Нам нужно навести порядок в инструкциях выше. Одним из распространенных механизмов является блокирование всех потоков, кроме одного. Pthread мьютекс использует этот механизм.
Любой поток, который должен выполнить некоторые строки кода, которые могут небезопасно изменять совместно используемые значения другими потоками одновременно (используя телефон, чтобы поговорить с его женой), должен сначала получить блокировку на мьютекс. Таким образом, любой поток, которому требуется доступ к общим данным, должен пройти через блокировку мьютекса. Только тогда поток сможет выполнить код. Этот раздел кода называется критическим разделом.
Как только поток выполнил критическую секцию, он должен снять блокировку с мьютекса, чтобы другой поток мог получить блокировку с мьютексом.
Концепция наличия мьютекса кажется несколько странной, если учесть, что люди ищут эксклюзивный доступ к реальным, физическим объектам, но при программировании мы должны быть намеренными. Параллельные потоки и процессы не имеют социального и культурного воспитания, которое мы делаем, поэтому мы должны заставить их обмениваться данными.
Итак, технически говоря, как работает мьютекс? Разве он не страдает от тех же рас, что мы упоминали ранее? Разве pthread_mutex_lock () не немного сложнее простого приращения переменной?
Технически говоря, нам нужна некоторая аппаратная поддержка, чтобы помочь нам. Разработчики аппаратного обеспечения дают нам машинные инструкции, которые делают больше чем одно, но гарантируют, что они будут атомарными. Классическим примером такой инструкции является тест-и-набор (TAS). При попытке получить блокировку ресурса мы могли бы использовать TAS, чтобы проверить, равно ли значение в памяти 0. Если это так, это будет нашим сигналом, что ресурс используется, и мы ничего не делаем (или, точнее, , мы ждем по какому-то механизму. Мьютекс pthreads поместит нас в специальную очередь в операционной системе и уведомит нас, когда ресурс станет доступным. Более тупые системы могут потребовать, чтобы мы выполняли жесткий цикл вращения, проверяя условие снова и снова) , Если значение в памяти не равно 0, TAS устанавливает местоположение в значение, отличное от 0, без использования каких-либо других инструкций. Это как объединение двух инструкций по сборке в 1, чтобы придать нам атомарность. Таким образом, тестирование и изменение значения (если изменение уместно) не может быть прервано после его начала. Мы можем создать мьютексы поверх такой инструкции.
Примечание: некоторые разделы могут выглядеть аналогично предыдущему ответу. Я принял его приглашение отредактировать, он предпочел оригинальную версию, так что я сохраняю то, что у меня было, и немного словоблудия.