Хотя мьютекс может использоваться для решения других проблем, основная причина, по которой они существуют, заключается в том, чтобы обеспечить взаимное исключение и тем самым решить то, что известно как состояние гонки. Когда два (или более) потока или процесса пытаются получить доступ к одной и той же переменной одновременно, у нас есть потенциал для состояния гонки. Рассмотрим следующий код
//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, чтобы придать нам атомарность. Таким образом, тестирование и изменение значения (если изменение уместно) не может быть прервано после его начала. Мы можем построить мьютексы поверх такой инструкции.
Примечание: некоторые разделы могут показаться похожими на более ранний ответ. Я принял его приглашение отредактировать, он предпочел оригинальный способ, так что я сохраняю то, что у меня было, и в нем немного словоблудия.