Это просто способ, которым переменные условия (или были изначально) реализованы.
Мьютекс используется для защиты самой переменной условия . Вот почему вам нужно заблокировать его, прежде чем ждать.
Ожидание «атомарно» разблокирует мьютекс, предоставляя другим доступ к переменной условия (для сигнализации). Затем, когда переменная условия будет сигнализирована или передана, один или несколько потоков в списке ожидания будут разбужены, и мьютекс снова будет магически заблокирован для этого потока.
Обычно вы видите следующую операцию с условными переменными, иллюстрирующую их работу. В следующем примере показан рабочий поток, которому дается работа через сигнал условной переменной.
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
do the work.
unlock mutex.
clean up.
exit thread.
Работа выполняется в этом цикле при условии, что некоторые из них будут доступны после возвращения ожидания. Когда поток помечен как прекращающий работу (обычно другой поток устанавливает условие выхода, а затем запускает переменную условия, чтобы разбудить этот поток), цикл завершается, мьютекс разблокируется и этот поток завершается.
Приведенный выше код является моделью для одного потребителя, так как мьютекс остается заблокированным во время работы. Для варианта с несколькими потребителями вы можете использовать, например :
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
copy work to thread local storage.
unlock mutex.
do the work.
lock mutex.
unlock mutex.
clean up.
exit thread.
что позволяет другим потребителям получать работу, пока этот делает работу.
Переменная условия освобождает вас от бремени опроса некоторого условия, позволяя другому потоку уведомлять вас, когда что-то должно произойти. Другой поток может сказать, что этот поток доступен следующим образом:
lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
Подавляющее большинство из того, что часто ошибочно называют ложными пробуждениями, как правило, всегда происходило из-за того, что в их pthread_cond_wait
вызове (широковещании) было сигнализировано несколько потоков , можно было вернуться с мьютексом, выполнить работу, а затем снова подождать.
Тогда вторая сигнальная нить могла выйти, когда не было никакой работы. Таким образом, у вас должна была быть дополнительная переменная, указывающая, что работа должна быть выполнена (это было изначально защищено мьютексом с помощью пары condvar / mutex - другие потоки должны были заблокировать мьютекс перед тем, как его изменить).
Это было технически возможно для нити , чтобы вернуться из состояния ожидания , не выгоняют другой процесс (это является подлинным поддельным будильником) , но во всех мои многих годах работают над Pthreads, как в развитии услуг / коды и как пользователь из них я ни разу не получил ни одного из них. Может быть, это только потому, что у HP была достойная реализация :-)
В любом случае, тот же код, который обрабатывал ошибочный случай, также обрабатывал подлинные ложные пробуждения, так как для них не был установлен флаг доступности работы.
do something
внутриwhile
цикла?Переменная условия довольно ограничена, если вы можете сигнализировать только об условии, обычно вам нужно обрабатывать некоторые данные, которые относятся к условию, о котором было сообщено. Сигнализация / пробуждение должны быть сделаны атомарно, чтобы достичь этого без введения условий гонки, или быть слишком сложными
По техническим причинам pthreads может дать вам ложное пробуждение . Это означает, что вам нужно проверить предикат, чтобы вы могли быть уверены, что условие действительно было сигнализировано - и отличить его от ложного пробуждения. Проверка такого условия в отношении его ожидания должна быть защищена - поэтому переменная условия нуждается в способе атомарного ожидания / пробуждения при блокировке / разблокировке мьютекса, охраняющего это условие.
Рассмотрим простой пример, когда вас уведомляют о том, что производятся некоторые данные. Может быть, другой поток сделал некоторые данные, которые вы хотите, и установить указатель на эти данные.
Представьте себе поток производителя, передающий некоторые данные другому потоку потребителя через указатель some_data.
вы, естественно, получите много расы, что если другой поток сделал
some_data = new_data
сразу после того, как вы проснулись, но до того, как вы сделалиdata = some_data
Вы не можете создать свой собственный мьютекс, чтобы защитить это дело .eg
Не сработает, все еще есть шанс расы между пробуждением и захватом мьютекса. Размещение мьютекса перед pthread_cond_wait вам не поможет, так как вы теперь будете удерживать мьютекс во время ожидания - т.е. производитель никогда не сможет захватить мьютекс. (обратите внимание, что в этом случае вы можете создать вторую переменную условия, чтобы сообщить производителю, с которым вы покончили
some_data
- хотя это станет сложным, особенно если вам нужно много производителей / потребителей.)Таким образом, вам нужен способ атомарного освобождения / захвата мьютекса при ожидании / пробуждении из состояния. Вот что делают условные переменные pthread, и вот что вы сделаете:
(Естественно, продюсер должен принять те же меры предосторожности, всегда охраняя 'some_data' с одним и тем же мьютексом, и следя за тем, чтобы он не перезаписывал some_data, если some_data в настоящее время есть! = NULL)
источник
while (some_data != NULL)
быть цикл do-while, чтобы он ожидал условную переменную хотя бы один раз?while(some_data != NULL)
бытьwhile(some_data == NULL)
?Переменные условия POSIX не имеют состояния. Так что вы несете ответственность за поддержание государства. Поскольку к состоянию будут обращаться как потоки, которые ожидают, так и потоки, которые сообщают другим потокам, чтобы они перестали ждать, оно должно быть защищено мьютексом. Если вы думаете, что можете использовать условные переменные без мьютекса, то вы еще не поняли, что условные переменные не имеют состояния.
Переменные условия строятся вокруг условия. Потоки, ожидающие условную переменную, ожидают некоторого условия. Потоки, которые сигнализируют переменные условия, изменяют это условие. Например, поток может ожидать поступления некоторых данных. Какой-то другой поток может заметить, что данные поступили. «Данные прибыли» - это условие.
Вот классическое использование условной переменной упрощенно:
Посмотрите, как нить ждет работы. Работа защищена мьютексом. Ожидание освобождает мьютекс, так что другой поток может дать этому потоку некоторую работу. Вот как это будет сигнализироваться:
Обратите внимание, что вам нужен мьютекс для защиты рабочей очереди. Обратите внимание, что сама переменная условия не имеет представления, есть ли работа или нет. То есть переменная условия должна быть связана с условием, это условие должно поддерживаться вашим кодом, и, поскольку оно совместно используется потоками, оно должно быть защищено мьютексом.
источник
Не все функции условных переменных требуют мьютекса: только операции ожидания. Сигнал и широковещательные операции не требуют мьютекса. Переменная условия также не постоянно связана с конкретным мьютексом; внешний мьютекс не защищает переменную условия. Если переменная условия имеет внутреннее состояние, например очередь ожидающих потоков, это должно быть защищено внутренней блокировкой внутри переменной условия.
Операции ожидания объединяют переменную условия и мьютекс, потому что:
По этой причине операция ожидания принимает в качестве аргументов как мьютекс, так и условие: так, чтобы она могла управлять атомарным переносом потока из владения мьютексом в ожидание, чтобы поток не становился жертвой состояния проигранной гонки пробуждения .
Потерянное состояние гонки пробуждения произойдет, если поток откажется от мьютекса, а затем ожидает объект синхронизации без состояния, но не атомарным способом: существует окно времени, когда поток больше не имеет блокировки и имеет еще не началось ожидание на объекте. Во время этого окна может войти другой поток, выполнить ожидаемое условие, сообщить о синхронизации без сохранения состояния и затем исчезнуть. Объект без состояния не помнит, что ему было сообщено (он не имеет состояния). Таким образом, исходный поток переходит в режим ожидания на объект синхронизации без сохранения состояния и не активируется, даже если условие, в котором оно нуждается, уже стало истинным: потеря пробуждения.
Функции ожидания условной переменной позволяют избежать потерянного пробуждения, убедившись, что вызывающий поток зарегистрирован, чтобы надежно перехватить пробуждение до того, как он откажется от мьютекса. Это было бы невозможно, если бы функция ожидания условной переменной не принимала мьютекс в качестве аргумента.
источник
pthread_cond_broadcast
иpthread_cond_signal
операции (о которых этот вопрос SO) даже не принимают мьютекс в качестве аргумента; только условие. Спецификация POSIX здесь . Мьютекс упоминается только в отношении того, что происходит в ожидающих потоках, когда они просыпаются.Я не нахожу другие ответы столь же краткими и удобочитаемыми, как эта страница . Обычно код ожидания выглядит примерно так:
Есть три причины, чтобы обернуть
wait()
в мьютекс:signal()
до этогоwait()
и мы бы пропустили это пробуждение.check()
зависит от модификации из другого потока, так что вам все равно нужно взаимное исключение.Третий момент не всегда вызывает озабоченность - исторический контекст связан со статьей и этим разговором .
В отношении этого механизма часто упоминаются ложные пробуждения (то есть ожидающий поток вызывается без вызова
signal()
). Однако такие события обрабатываются зацикленнымиcheck()
.источник
Переменные условия связаны с мьютексом, потому что это единственный способ избежать гонки, для которой он предназначен.
На данный момент нет потока, который будет сигнализировать переменную условия, поэтому thread1 будет ждать вечно, даже если protectedReadyToRunVariable говорит, что он готов к работе!
Единственный способ избежать этого - условные переменные атомарно освобождают мьютекс, одновременно начав ожидать переменную условия. Вот почему функция cond_wait требует мьютекса
источник
Предполагается, что мьютекс будет заблокирован, когда вы звоните
pthread_cond_wait
; когда вы вызываете его, он атомарно разблокирует мьютекс, а затем блокирует условие. Как только условие сигнализируется, оно снова блокируется и возвращается.Это позволяет реализовать предсказуемое планирование, если это необходимо, тем, что поток, который будет выполнять сигнализацию, может ожидать освобождения мьютекса, чтобы выполнить его обработку, а затем сигнализировать о состоянии.
источник
Я сделал упражнение в классе, если вы хотите реальный пример условной переменной:
источник
Похоже, это конкретное дизайнерское решение, а не концептуальная необходимость.
Согласно документам pthreads, причина того, что мьютекс не был разделен, заключается в том, что благодаря их объединению происходит значительное улучшение производительности, и они ожидают, что из-за общих условий гонки, если вы не используете мьютекс, это почти всегда будет выполняться в любом случае.
https://linux.die.net/man/3/pthread_cond_wait
источник
Есть множество толкований об этом, но я хочу выразить это примером, приведенным ниже.
Что не так с фрагментом кода? Просто подумайте, прежде чем идти вперед.
Проблема действительно тонкая. Если родитель вызывает
thr_parent()
и затем проверяет значениеdone
, он увидит, что это так,0
и, таким образом, попытается заснуть. Но как раз перед вызовом wait для засыпания родитель прерывается между строками 6-7, а потом бежит. Дочерний объект изменяет переменную состоянияdone
на1
и передает сигналы, но ни один поток не ожидает, и, таким образом, ни один поток не пробуждается. Когда родитель снова бежит, он спит вечно, что действительно вопиюще.Что делать, если они выполняются при приобретении замков индивидуально?
источник