Это использование условных переменных ВСЕГДА подвержено гонке с потерянным сигналом? - PullRequest
3 голосов
/ 24 сентября 2011

Предположим, что условная переменная используется в ситуации, когда сигнальный поток изменяет состояние, влияющее на значение истинности предиката, и вызывает pthread_cond_signal без удержания мьютекса, связанного с условной переменной? Правда ли, что этот тип использования всегда в зависимости от условий гонки, когда сигнал может быть пропущен?

Мне кажется, что всегда существует очевидная раса:

  1. Официант оценивает предикат как ложный, но прежде чем он сможет начать ждать ...
  2. Другой поток изменяет состояние таким образом, что делает предикат истинным.
  3. Этот другой поток вызывает pthread_cond_signal, который ничего не делает, потому что официантов еще нет.
  4. Поток официанта входит в pthread_cond_wait, не подозревая, что предикат теперь верен, и ждет бесконечно.

Но всегда ли существует такой же тип расы, если ситуация изменяется так, что либо (A) мьютекс удерживается при вызове pthread_cond_signal, просто не при изменении состояния, либо (B), чтобы мьютекс был удерживается при изменении состояния, только не при вызове pthread_cond_signal?

Я спрашиваю с точки зрения желания узнать, существуют ли какие-либо допустимые варианты использования вышеперечисленных случаев использования, не соответствующих передовым методам, то есть, должна ли правильная реализация переменной условия учитывать такие случаи, чтобы избежать самих условий гонки, или может ли он игнорировать их, потому что они уже по своей природе являются грубыми.

Ответы [ 3 ]

2 голосов
/ 24 сентября 2011

Фундаментальная гонка здесь выглядит следующим образом:

THREAD A        THREAD B
Mutex lock
Check state
                Change state
                Signal
cvar wait
(never awakens)

Если мы возьмем блокировку ИЛИ при изменении состояния, ИЛИ сигнал, ИЛИ оба, тогда мы избежим этого;изменение состояния и сигнал не могут происходить, пока поток A находится в критической секции и удерживает блокировку.

Если мы рассмотрим обратный случай, когда поток A перемежается с потоком B, нетпроблема:

THREAD A        THREAD B
                Change state
Mutex lock
Check state
( no need to wait )
Mutex unlock
                Signal (nobody cares)

Так что нет особой необходимости в том, чтобы поток B содержал мьютекс во всей операции;ему просто нужно удерживать мьютекс на некотором, возможно, бесконечно малом интервале между изменением состояния и сигналом.Конечно, если само состояние требует блокировки для безопасной манипуляции, тогда блокировку также нужно удерживать и при изменении состояния.

Наконец, обратите внимание, что раннее удаление мьютекса вряд ли будет улучшением производительностив большинстве случаев.Требование удержания мьютекса снижает конфликты по поводу внутренних блокировок в переменной условия, и в современных реализациях pthreads система может «переместить» ожидающий поток из ожидания в cvar в ожидание в мьютексе без его пробуждения (таким образом, избегая егопробуждение только для немедленного блокирования мьютекса). Как указано в комментариях, удаление мьютекса может в некоторых случаях повысить производительность, уменьшив количество необходимых системных вызовов.С другой стороны, это также может привести к дополнительному конфликту во внутреннем мьютексе условной переменной.Тяжело сказать.В любом случае, не стоит об этом беспокоиться.

Обратите внимание, что применимые стандарты требуют, чтобы pthread_cond_signal был безопасно вызываться без удержания мьютекса:

.Функции pthread_cond_signal () или pthread_cond_broadcast () могут вызываться потоком независимо от того, владеет ли он в настоящее время мьютексом, который потоки, вызывающие pthread_cond_wait () или pthread_cond_timedwait (), связали с переменной условия во время ожидания [...]

Это обычно означает, что условные переменные имеют внутреннюю блокировку над своими внутренними структурами данных или иным образом используют очень осторожный алгоритм без блокировки.

2 голосов
/ 24 сентября 2011

Состояние должно быть изменено внутри мьютекса, если только по какой-либо другой причине, кроме возможности ложных пробуждений, которые могут привести к тому, что читатель прочитает состояние, пока писатель находится в процессе его написания.

Вы можете позвонить pthread_cond_signal в любое время после изменения состояния. Это не должно быть внутри мьютекса. POSIX гарантирует, что по крайней мере один официант проснется, чтобы проверить новое состояние. Подробнее к делу:

  1. Вызов pthread_cond_signal не гарантирует, что читатель первым получит мьютекс. Другой писатель может войти до того, как читатель сможет проверить новый статус. Переменные условия не гарантируют, что читатели будут следовать непосредственно за авторами (в конце концов, что, если читателей нет?)
  2. Вызывать его после снятия блокировки на самом деле лучше, поскольку вы не рискуете, чтобы только что проснувшийся читатель немедленно вернулся в режим сна, пытаясь получить блокировку, которую по-прежнему держит писатель.

РЕДАКТИРОВАТЬ: @DietrichEpp делает хорошие замечания в комментариях. Автор должен изменить состояние таким образом, чтобы читатель никогда не мог получить доступ к несовместимому состоянию. Это можно сделать либо путем получения мьютекса, используемого в условной переменной, как я указал выше, либо путем обеспечения того, что все изменения состояния являются атомарными.

1 голос
/ 29 марта 2012

Ответ таков: есть раса, и для ее устранения вы должны сделать следующее:

/* atomic op outside of mutex, and then: */

pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);

pthread_cond_signal(&c);

Защита данных не имеет значения, поскольку вы не держите мьютекспри вызове pthread_cond_signal в любом случае.

Видите, блокируя и разблокируя мьютекс, вы создали барьер.В тот короткий момент, когда у связующего есть мьютекс, есть определенность: ни у какого другого потока нет мьютекса.Это означает, что ни один другой поток не выполняет какие-либо критические области.

Это означает, что все потоки либо собираются получить мьютекс для обнаружения изменений, которые вы опубликовали, либо они уже нашли это изменение и убежали с ним(освобождая мьютекс), или еще не нашли, что они ищут и атомарно отказались от мьютекса, чтобы уйти спать (и гарантированно ожидают приятного ожидания при условии).

Без блокировки мьютекса /разблокировать, у вас нет синхронизации.Сигнал иногда срабатывает, поскольку потоки, которые не видели измененного атомарного значения, переходят в свой атомарный сон, чтобы дождаться его.

Так вот, что делает мьютекс с точки зрения потока, который являетсясигнализации.Вы можете получить атомарность доступа от чего-то другого, но не от синхронизации.

PS Я реализовал эту логику раньше.Ситуация была в ядре Linux (используя мои собственные мьютексы и условные переменные).

В моей ситуации было невозможно для сигнализатора удерживать мьютекс для атомарной операции над общими данными,Зачем?Поскольку сигнализатор выполнял операцию в пользовательском пространстве, внутри буфера, совместно используемого ядром и пользователем, а затем (в некоторых ситуациях) выполнял системный вызов в ядре для пробуждения потока.Пространство пользователя просто внесло некоторые изменения в буфер, и затем, если бы некоторые условия были выполнены, он выполнил ioctl.

Так что в вызове ioctl я сделал блокировку / разблокировку мьютекса, а затемнажмите переменную условия.Это гарантировало, что поток не пропустит пробуждение, связанное с этой последней модификацией, опубликованной пользовательским пространством.

Сначала у меня просто был сигнал переменной условия, но это выглядело неправильно без участия мьютекса, поэтому яНемного рассуждал о ситуации и понял, что мьютекс должен быть просто заблокирован и разблокирован, чтобы соответствовать ритуалу синхронизации, который устраняет потерянный пробуждение.

...