Виклик pthread_cond_signal без блокування mutex


83

Я десь читав, що нам слід заблокувати мьютекс перед викликом pthread_cond_signal і розблокувати мьютекс після його виклику:

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

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

Відповіді:


140

Якщо ви не зафіксуєте мьютекс у кодовій стежці, що змінює стан і сигнали, ви можете втратити активацію. Розглянемо цю пару процесів:

Процес A:

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(), але це може призвести до менш оптимального планування потоків, і ви обов'язково заблокували мьютекс вже в цьому шляху коду через зміну самої умови).


3
@ Примітка: Так, у "правильному" шляху шлях pthread_signal_cond() може бути переміщений після розблокування мьютексу, хоча, мабуть, краще не робити. Можливо, правильніше сказати, що в той момент, коли ви телефонуєте pthread_signal_cond(), вам вже потрібно було заблокувати мьютекс, щоб змінити саму умову.
кафе

@Nemo: Так, код зламаний, саме тому він позначений як "неправильний" - це мій досвід, що люди, які задають це запитання, часто розглядають можливість залишити блокування повністю на сигнальному шляху. Можливо, ваш досвід відрізняється.
кафе

9
Наскільки це варте, здається, питання, чи потрібно розблокувати до або після сигналу, насправді є нетривіальним для відповіді. Розблокування після гарантує, що потік з нижчим пріоритетом не зможе викрасти подію з вищого пріоритету, але якщо ви не використовуєте пріоритети, розблокування до сигналу фактично зменшить кількість системних викликів / перемикачів контексту та покращити загальну продуктивність.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

1
@R .. У вас це назад. Розблокування перед сигналом збільшує кількість системних дзвінків і знижує загальну продуктивність. Якщо ви подаєте сигнал до розблокування, реалізація знає, що сигнал не може пробудити потік (оскільки будь-який потік, заблокований на cv, потребує мьютексу для просування вперед), що дозволяє йому використовувати швидкий шлях. Якщо ви подаєте сигнал після розблокування, і розблокування, і сигнал можуть активувати потоки, тобто це обоє дорогі операції.
Девід Шварц,

1
@Alceste_: Дивіться цей текст у POSIX : "Коли потік очікує на змінну умови, вказавши певний мьютекс pthread_cond_timedwait()або для pthread_cond_wait()операції, або між операцією, між цим мьютексом і змінною умови формується динамічне прив'язування, яке залишається в силі до тих пір, поки принаймні один потік заблокований для змінної умови. Протягом цього часу ефект спроби будь-якого потоку зачекати на цю умовну змінну, використовуючи інший мьютекс, не визначений. "
caf

49

Відповідно до цього посібника:

Ці pthread_cond_broadcast()або pthread_cond_signal()функції можуть бути викликані волоском чи ні в даний час володіє м'ютексів , що потоки закликають pthread_cond_wait() або pthread_cond_timedwait()асоціювали зі змінним станом під час їх чекають; однак, якщо потрібна передбачувана поведінка планування, тоді цей мьютекс повинен бути заблокований потоком, що викликає pthread_cond_broadcast()або pthread_cond_signal().

Значення передбачуваного викладу поведінки планування було пояснене Дейвом Бутенгофом (автором Програмування з потоками POSIX ) на comp.programming.threads і доступне тут .


6
+1 за посилання пошти від Дейва Бутенгофа. Я завжди дивувався цьому питанню, тепер я знаю ... Сьогодні дізнався щось важливе. Дякую.
Nils Pipenbrinck

Якщо потрібна передбачувана поведінка планування, поставте оператори в один потік у бажаному порядку або використовуйте пріоритети потоків.
Каз

7

caf, у зразковому коді процес B модифікується, conditionне блокуючи мутекс спочатку. Якби Процес B просто заблокував мьютекс під час цієї модифікації, а потім все-таки розблокував мьютекс перед викликом pthread_cond_signal, не було б жодних проблем - чи я правий у цьому?

Я інтуїтивно вважаю, що позиція caf правильна: дзвонити, pthread_cond_signalне маючи блокування mutex, - погана ідея. Але приклад кафе насправді не є підтвердженням цієї позиції; це просто доказ на підтримку набагато слабшої (практично самоочевидної) позиції, що поганою ідеєю є зміна спільного стану, захищеного мьютексом, якщо ви не заблокували цей мьютекс спочатку.

Хто-небудь може надати зразок коду, у якому виклик з pthread_cond_signalподальшим результатом pthread_mutex_unlockдає правильну поведінку, а виклик з pthread_mutex_unlockподальшим результатом pthread_cond_signalдає неправильну поведінку?


3
Насправді, я думаю, що моє запитання є дублікатом цього , і відповідь: "Це добре, ви можете повністю зателефонувати pthread_cond_signal, не володіючи блокуванням мьютексу. Проблеми з коректністю немає. Але в загальних реалізаціях ви пропустите розумну оптимізацію глибоко всередині pthreads, тому трохи переважніше викликати pthread_cond_signal, утримуючи замок. "
Quuxplusone

Я зробив це зауваження в останньому абзаці своєї відповіді.
кафе

У вас є хороший сценарій тут: stackoverflow.com/a/6419626/1284631 Зверніть увагу, що він не стверджує, що поведінка є неправильною, він представляє лише випадок, коли поведінка може бути не такою, як очікувалося.
користувач1284631

Можна створити зразок коду, де виклик pthread_cond_signalпісля pthread_mutex_unlockможе призвести до втрати пробудження, оскільки сигнал потрапляє в "неправильний" потік, який блокується після побачення зміни в предикаті. Це проблема лише в тому випадку, якщо одну і ту ж змінну умови можна використовувати для кількох предикатів, а ви не використовуєте pthread_cond_broadcast, що в будь-якому випадку є рідкісним і крихким шаблоном.
Девід Шварц,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.