Я новичок в многопоточности и пытался понять, как работают мьютексы. Я много гуглил, но это все еще оставляло некоторые сомнения в том, как это работает, потому что я создал свою собственную программу, в которой блокировка не работала.
Один абсолютно неинтуитивный синтаксис мьютекса - pthread_mutex_lock( &mutex1 );
это когда мьютекс блокируется, когда я действительно хочу заблокировать какую-то другую переменную. Означает ли этот синтаксис, что блокировка мьютекса блокирует область кода, пока мьютекс не будет разблокирован? Тогда откуда потоки узнают, что регион заблокирован? [ ОБНОВЛЕНИЕ: потоки знают, что область заблокирована, Ограждение памяти ]. И разве такое явление не должно называться критическим разделом? [ ОБНОВЛЕНИЕ: объекты критических разделов доступны только в Windows, где объекты быстрее мьютексов и видны только потоку, который их реализует. В противном случае критический раздел относится только к области кода, защищенной мьютексом. ]
Короче, не могли бы вы помочь с примером простейшего примера мьютекса и простейшим объяснением логики его работы? Я уверен, что это поможет многим другим новичкам.
источник
Ответы:
Вот моя скромная попытка объяснить концепцию новичкам по всему миру: ( версия с цветовой кодировкой в моем блоге тоже)
Многие люди бегут к одинокой телефонной будке (у них нет мобильных телефонов), чтобы поговорить со своими близкими. Первый, кто поймает дверную ручку будки, это тот, кому разрешено пользоваться телефоном. Он должен держаться за ручку двери, пока он пользуется телефоном, в противном случае кто-то другой схватится за ручку, выбросит его и поговорит с женой :) Система очередей как таковая отсутствует. Когда человек заканчивает разговор, выходит из кабины и покидает дверную ручку, следующий человек, который схватит дверную ручку, получит право пользоваться телефоном.
Нить является: Каждый человек мьютекс является следующим : дверная ручка замок является: рука человека ресурс является: Телефон
Любой поток, который должен выполнить некоторые строки кода, которые не должны изменяться другими потоками одновременно (с помощью телефона, чтобы поговорить с его женой), должен сначала получить блокировку на мьютексе (сжимая дверную ручку кабины ). Только тогда поток сможет запустить эти строки кода (сделать телефонный звонок).
После того, как поток выполнил этот код, он должен снять блокировку с мьютекса, чтобы другой поток мог получить блокировку с мьютексом (другие люди смогут получить доступ к телефонной будке).
[ Концепция наличия мьютекса несколько абсурдна, если рассматривать реальный монопольный доступ, но в мире программирования, я думаю, не было другого способа позволить другим потокам «увидеть», что поток уже выполняет некоторые строки кода. Существуют концепции рекурсивных мьютексов и т. Д., Но этот пример предназначен только для того, чтобы показать вам базовую концепцию. Надеюсь, что пример дает вам четкое представление о концепции. ]
С C ++ 11 потоков:
Скомпилируйте и запустите используя
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Вместо явного использования
lock
иunlock
, вы можете использовать скобки, как показано здесь , если вы используете блокировку с ограничением по объему для предоставляемого преимущества . Однако замки с ограниченным пространством имеют незначительную производительность.источник
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. Что касается использования блокировки по областям, то это зависит от разработчика, в зависимости от того, какое приложение они создают. Этот ответ предназначался для понимания базового понимания концепции мьютекса, а не для понимания всех его сложностей, поэтому ваши комментарии и ссылки приветствуются, но немного выходят за рамки данного руководства.Хотя мьютекс может использоваться для решения других проблем, основная причина, по которой они существуют, заключается в том, чтобы обеспечить взаимное исключение и тем самым решить то, что известно как состояние гонки. Когда два (или более) потока или процесса пытаются получить доступ к одной и той же переменной одновременно, у нас есть потенциал для состояния гонки. Рассмотрим следующий код
Внутренние элементы этой функции выглядят так просто. Это только одно утверждение. Однако типичный эквивалент псевдо-ассемблера может быть следующим:
Поскольку все эквивалентные инструкции на языке ассемблера требуются для выполнения операции приращения над i, мы говорим, что приращение i - неатмосичная операция. Элементарная операция - это операция, которая может быть выполнена на оборудовании с гарантией того, что она не будет прервана после начала выполнения инструкции. Инкремент i состоит из цепочки из 3 атомарных инструкций. В параллельной системе, где несколько потоков вызывают функцию, возникают проблемы, когда поток читает или пишет в неправильное время. Представьте, что у нас одновременно работают два потока, и один вызывает функцию сразу после другого. Предположим также, что мы инициализировали значение 0. Также предположим, что у нас много регистров и что два потока используют совершенно разные регистры, поэтому коллизий не будет. Фактическое время этих событий может быть:
Случилось так, что у нас есть два потока, которые увеличиваются одновременно, наша функция вызывается дважды, но результат несовместим с этим фактом. Похоже, функция была вызвана только один раз. Это связано с тем, что атомарность «нарушена» на уровне машины, то есть потоки могут прерывать друг друга или работать вместе в неподходящее время.
Нам нужен механизм, чтобы решить это. Нам нужно навести порядок в инструкциях выше. Одним из распространенных механизмов является блокирование всех потоков, кроме одного. Pthread мьютекс использует этот механизм.
Любой поток, который должен выполнить некоторые строки кода, которые могут небезопасно изменять совместно используемые значения другими потоками одновременно (используя телефон, чтобы поговорить с его женой), должен сначала получить блокировку на мьютекс. Таким образом, любой поток, которому требуется доступ к общим данным, должен пройти через блокировку мьютекса. Только тогда поток сможет выполнить код. Этот раздел кода называется критическим разделом.
Как только поток выполнил критическую секцию, он должен снять блокировку с мьютекса, чтобы другой поток мог получить блокировку с мьютексом.
Концепция наличия мьютекса кажется немного странной, когда мы рассматриваем людей, которые ищут эксклюзивный доступ к реальным, физическим объектам, но при программировании мы должны быть намеренными. Параллельные потоки и процессы не имеют социального и культурного воспитания, которое мы делаем, поэтому мы должны заставить их обмениваться данными.
Технически говоря, как работает мьютекс? Разве он не страдает от тех же рас, что мы упоминали ранее? Разве pthread_mutex_lock () не немного сложнее простого приращения переменной?
Технически говоря, нам нужна некоторая аппаратная поддержка, чтобы помочь нам. Разработчики оборудования дают нам машинные инструкции, которые делают больше, чем одно, но гарантируют, что они будут атомарными. Классическим примером такой инструкции является тест-набор (TAS). При попытке получить блокировку ресурса мы могли бы использовать TAS, чтобы проверить, равно ли значение в памяти 0. Если это так, это будет нашим сигналом того, что ресурс используется, и мы ничего не делаем (или точнее , мы ждем по какому-то механизму. Мьютекс pthreads поместит нас в специальную очередь в операционной системе и уведомит нас, когда ресурс станет доступным. Более тупые системы могут потребовать, чтобы мы выполняли жесткий цикл вращения, проверяя условие снова и снова) , Если значение в памяти не равно 0, TAS устанавливает местоположение в значение, отличное от 0, без использования каких-либо других инструкций. Это' Это как объединение двух инструкций по сборке в 1, чтобы придать нам атомарность. Таким образом, тестирование и изменение значения (если изменение уместно) не может быть прервано после его начала. Мы можем построить мьютексы поверх такой инструкции.
Примечание: некоторые разделы могут показаться похожими на более ранний ответ. Я принял его приглашение отредактировать, он предпочел оригинальный способ, так что я сохраняю то, что у меня было, и в нем немного словоблудия.
источник
Лучшее учебное пособие по темам, которое я знаю, находится здесь:
https://computing.llnl.gov/tutorials/pthreads/
Мне нравится, что он написан об API, а не о конкретной реализации, и дает несколько хороших простых примеров, которые помогут вам понять синхронизацию.
источник
Недавно я наткнулся на этот пост и думаю, что ему нужно обновленное решение для мьютекса c ++ 11 стандартной библиотеки (а именно std :: mutex).
Я вставил немного кода ниже (мои первые шаги с мьютексом - я изучил параллелизм на win32 с HANDLE, SetEvent, WaitForMultipleObjects и т. Д.).
Так как это моя первая попытка с std :: mutex и друзьями, я хотел бы видеть комментарии, предложения и улучшения!
источник
Функция
pthread_mutex_lock()
либо получает мьютекс для вызывающего потока, либо блокирует поток, пока мьютекс не будет получен. Связанноеpthread_mutex_unlock()
освобождает мьютекс.Думайте о мьютексе как об очереди; каждый поток, который пытается получить мьютекс, будет помещен в конец очереди. Когда поток освобождает мьютекс, следующий поток в очереди отключается и теперь работает.
Критический участок относится к области кода , где недетерминизм возможно. Часто это происходит потому, что несколько потоков пытаются получить доступ к общей переменной. Критическая секция небезопасна, пока не установлена какая-то синхронизация. Блокировка мьютекса - это одна из форм синхронизации.
источник
Вы должны проверить переменную мьютекса перед использованием области, защищенной мьютексом. Таким образом, ваш pthread_mutex_lock () может (в зависимости от реализации) дождаться освобождения mutex1 или вернуть значение, указывающее, что блокировка не может быть получена, если кто-то уже заблокировал ее.
Mutex - это просто упрощенный семафор. Если вы читаете о них и понимаете их, вы понимаете мьютексы. Есть несколько вопросов, касающихся мьютексов и семафоров в SO. Разница между двоичным семафором и мьютексом , когда мы должны использовать мьютекс, когда мы должны использовать семафор и так далее. Пример туалета в первой ссылке примерно такой же хороший пример, как можно себе представить. Весь код проверяет наличие ключа и резервирует его. Обратите внимание, что вы действительно не бронируете сам туалет, а ключ.
источник
pthread_mutex_lock
не может вернуться, если кто-то еще держит замок. Это блокирует в этом случае, и в этом весь смысл.pthread_mutex_trylock
это функция, которая будет возвращаться, если блокировка удерживается.Для тех, кто ищет пример мьютекса с шортексом:
источник
Пример SEMAPHORE ::
Ссылка: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
источник