- Почему в книге говорится, что pthread_cond_signal должен выполняться с блокировкой, удерживаемой для предотвращения гонки данных? Я не был уверен, поэтому я сослался на этот вопрос (и этот вопрос тоже), который в основном сказал: «Нет, это не обязательно». Почему возникает состояние гонки?
В книге, не содержащей полного примера, я думаю, что предполагаемый смысл в том, что может быть гонка данных с самим резюме, если оно сигнализируется без удержания соответствующего мьютекса. Это может иметь место в некоторых реализациях CV, но в книге конкретно говорится о pthreads, и CV pthreads не подвержены такому ограничению. Также как и C ++ std::condition_variable
, о котором говорят два других SO вопроса, о которых вы говорили. Таким образом, в этом смысле, книга просто неправильна .
Это правда, что можно привести примеры плохого использования CV, в сочетании с которым передача сигналов под защитой соответствующего мьютекса в значительной степени защищает от гонки данных, но передача сигналов без такой защиты подвержена гонкам данных. Но в таком случае ошибка не в самой сигнализации, а в ожидании, и если это то, что означает книга, тогда она обманчиво сформулирована . И, вероятно, все еще не так.
Что и где описывается состояние расы?
Можно только догадываться, что имел в виду автор.
Для протокола: Правильное использование условных переменных включает в себя, прежде всего, определение того, какое условие нужно обеспечить до выполнения выполнения. Это условие обязательно будет включать общие переменные, иначе нет никаких оснований ожидать, что что-то, что делает другой поток, может измениться, если условие выполнено. В таком случае весь доступ к совместно используемым переменным должен быть защищен мьютексом, если более одного потока живы.
Этот мьютекс должен, во-вторых, также быть тем, который связан с CV, и Потоки должны ждать резюме только во время удержания мьютекса. Это требование каждой реализации CV, которую я знаю, и она защищает от пропущенных сигналов и возможного тупика, возникающего в результате этого. Рассмотрим этот ошибочный и несколько надуманный пример:
// BAD
int temp;
result = pthread_mutex_lock(m);
// handle failure results ...
temp = shared;
result = pthread_mutex_unlock(m);
// handle failure results ...
if (temp == 0) {
result = pthread_cond_wait(cv, m);
// handle failure results ...
}
// do something ...
Предположим, что ему было разрешено ждать резюме без удержания мьютекса, как это делает этот код. Этот код предполагает, что в какой-то момент в будущем другой поток (T2) обновит shared
(под защитой мьютекса) и затем подаст сигнал CV, чтобы сообщить ожидающему (T1), что он может продолжить работу. Но что, если T2 делает это между тем, когда T1 разблокирует мьютекс и когда он начинает свое ожидание? Не имеет значения, сигнализирует ли T2 CV под защитой мьютекса или нет - T1 начнет ждать сигнала, который уже был доставлен. И сигналы CV не ставятся в очередь.
Итак, предположим, что T1 ожидает только под защитой мьютекса, как это фактически требуется. Этого не достаточно. Примите во внимание следующее:
// ALSO BAD
result = pthread_mutex_lock(m);
// handle failure results ...
if (shared == 0) {
result = pthread_cond_wait(cv, m);
// handle failure results ...
}
result = pthread_mutex_unlock(m);
// handle failure results ...
// do something ...
Это все еще неправильно, потому что это не надежно препятствует тому, чтобы T1 продолжил ожидание, когда интересующее условие неудовлетворено. Такой сценарий может возникать из
- сигнала, который был законно отправлен и получен, даже если конкретное условие, представляющее интерес для T1, не выполняется
- из сигнала, который был законно отправлен и получен, и условие выполняется при отправке сигнала, но T2 или другой поток снова изменяет общую переменную, прежде чем T1 возвращается из своего ожидания.
- ложное возвращение из ожидания, что очень редко, но иногда случается во многих реальных реализации -world.
Ничто из этого не зависит от того, отправляет ли T2 сигнал без защиты мьютекса.
правильный способ ожидания на переменная условия должна проверять состояние интереса перед ожиданием, а затем до l oop back и check снова перед продолжением:
// OK
result = pthread_mutex_lock(m);
// handle failure results ...
while (shared == 0) { // <-- 'while', not 'if'
result = pthread_cond_wait(cv, m);
// handle failure results ...
}
// typically, shared = 0 at this point
result = pthread_mutex_unlock(m);
// handle failure results ...
// do something ...
Иногда может случиться так, что поток T1, выполняющий этот код, вернется из своего ожидания, когда условие не выполнено, но если когда-либо это произойдет, он просто вернется к ожиданию, а не продолжит, когда этого не следует делать. Если другие потоки сигнализируют только под защитой мьютекса, это должно быть редким, но все же возможным. Если другие потоки сигнализируют без защиты мьютекса, T1 может просыпаться чаще, чем это строго необходимо, но при этом не происходит гонки данных и отсутствует риск неправильного поведения.