Вызов pthread_cond_signal без блокировки мьютекса

85

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

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

Мой вопрос: нельзя ли вызывать методы pthread_cond_signal или pthread_cond_broadcast без блокировки мьютекса?

Б. Фейли
источник

Ответы:

143

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

Процесс А:

pthread_mutex_lock(&mutex);
while (condition == FALSE)
    pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

Процесс B (неверно):

condition = TRUE;
pthread_cond_signal(&cond);

Затем рассмотрите это возможное чередование инструкций, которое conditionначинается как FALSE:

Process A                             Process B

pthread_mutex_lock(&mutex);
while (condition == FALSE)

                                      condition = TRUE;
                                      pthread_cond_signal(&cond);

pthread_cond_wait(&cond, &mutex);

conditionТеперь TRUE, но процесс А застревают ожидания на переменном состоянии - это пропущенный сигнал пробуждения. Если мы изменим процесс B, чтобы заблокировать мьютекс:

Процесс B (правильный):

pthread_mutex_lock(&mutex);
condition = TRUE;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

... тогда вышеуказанное не может произойти; пробуждение никогда не будет пропущено.

(Обратите внимание, что вы можете переместить сам pthread_cond_signal()объект после pthread_mutex_unlock(), но это может привести к менее оптимальному планированию потоков, и вы обязательно заблокировали мьютекс уже в этом пути кода из-за изменения самого условия).

кафе
источник
4
@Nemo: Да, по "правильному" пути объект pthread_signal_cond() можно переместить после разблокировки мьютекса, хотя, вероятно, лучше этого не делать. Возможно, правильнее будет сказать, что в момент, когда вы вызываете pthread_signal_cond(), вам уже нужно было заблокировать мьютекс, чтобы изменить само условие.
caf
@Nemo: Да, код не работает, поэтому он помечен как «неправильный» - по моему опыту, люди, которые задают этот вопрос, часто рассматривают возможность оставить блокировку полностью на сигнальном пути. Возможно, ваш опыт отличается.
кафе,
10
Как бы то ни было, кажется, что вопрос о том, следует ли разблокировать до или после сигнала, на самом деле нетривиальный ответ. Разблокировка после гарантирует, что поток с более низким приоритетом не сможет украсть событие у потока с более высоким приоритетом, но если вы не используете приоритеты, разблокировка до сигнала фактически уменьшит количество системных вызовов / переключений контекста и улучшить общую производительность.
R .. GitHub НЕ ПОМОГАЕТ ICE
1
@R .. У тебя все наоборот. Разблокировка до сигнала увеличивает количество системных вызовов и снижает общую производительность. Если вы подаете сигнал до разблокировки, реализация знает, что сигнал не может разбудить поток (потому что любой поток, заблокированный на cv, нуждается в мьютексе для продвижения вперед), что позволяет ему использовать быстрый путь. Если вы подаете сигнал после разблокировки, и разблокировка, и сигнал могут разбудить потоки, что означает, что это обе дорогостоящие операции.
Дэвид Шварц
1
@Alceste_: См. Этот текст в POSIX : «Когда поток ожидает переменной условия, указав конкретный мьютекс для операции pthread_cond_timedwait()или pthread_cond_wait()операции, между этим мьютексом и переменной условия формируется динамическая привязка, которая остается в силе до тех пор, пока в по крайней мере один поток заблокирован для переменной условия. В течение этого времени эффект от попытки любого потока ожидать для этой переменной условия с использованием другого мьютекса не определен. "
caf
51

Согласно этому руководству:

Эти pthread_cond_broadcast()или pthread_cond_signal()функции могут быть вызваны волоском ли или нет в настоящее время владеет мьютексом , что потоки призывающие pthread_cond_wait() или pthread_cond_timedwait()ассоциировали с переменным состоянием во время их ждут; однако, если требуется предсказуемое поведение при планировании, то этот мьютекс должен быть заблокирован потоком, вызывающим pthread_cond_broadcast()или pthread_cond_signal().

Значение оператора предсказуемого расписания объяснил Дэйв Бутенхоф (автор книги « Программирование с помощью потоков POSIX» ) на comp.programming.threads и доступен здесь .

ледовое преступление
источник
6
+1 за ссылку на письмо от Дэйва Бутенхофа. Я всегда сам задавался вопросом об этом, теперь знаю ... Сегодня узнал кое-что важное. Благодарю.
Нильс Пипенбринк
Если требуется предсказуемое поведение при планировании, поместите операторы в один поток в нужном вам порядке или используйте приоритеты потоков.
Kaz
7

caf, в вашем примере кода процесс B модифицируется conditionбез предварительной блокировки мьютекса. Если бы процесс B просто заблокировал мьютекс во время этой модификации, а затем все еще разблокировал мьютекс перед вызовом pthread_cond_signal, не было бы проблем - я прав в этом?

Я интуитивно считаю, что позиция caf верна: вызов pthread_cond_signalбез блокировки мьютекса - плохая идея. Но пример caf на самом деле не является доказательством в поддержку этой позиции; это просто свидетельство в поддержку гораздо более слабой (практически самоочевидной) позиции о том, что изменение общего состояния, защищенного мьютексом, является плохой идеей, если вы сначала не заблокировали этот мьютекс.

Может ли кто-нибудь предоставить образец кода, в котором вызов, pthread_cond_signalза которым следует, pthread_mutex_unlockдает правильное поведение, но вызов, pthread_mutex_unlockза которым следует, pthread_cond_signalдает неправильное поведение?

Quuxplusone
источник
3
На самом деле, я думаю, что мой вопрос является дубликатом этого , и ответ: «Все в порядке, вы можете полностью вызвать pthread_cond_signal, не владея блокировкой мьютекса. Нет проблемы с правильностью. Но в обычных реализациях вы упустите умную оптимизацию. глубоко внутри pthreads, поэтому несколько предпочтительнее вызывать pthread_cond_signal, все еще удерживая блокировку ".
Quuxplusone
Я сделал это наблюдение в последнем абзаце своего ответа.
caf
Здесь у вас есть хороший сценарий: stackoverflow.com/a/6419626/1284631 Обратите внимание, что он не утверждает, что поведение некорректно, а только представляет случай, когда поведение может быть не таким, как ожидалось.
user1284631
Можно создать пример кода, в котором вызов pthread_cond_signalafter pthread_mutex_unlockможет привести к потере пробуждения, потому что сигнал перехватывается «неправильным» потоком, который заблокирован после просмотра изменения в предикате. Это проблема, только если одна и та же переменная условия может использоваться более чем для одного предиката, а вы не используете pthread_cond_broadcast, что в любом случае является редкой и хрупкой схемой.
Дэвид Шварц