Между условной переменной и мьютексом существует естественная связь. Из этого наблюдения можно сделать такой вывод: все потоки, которые одновременно ожидают определенную условную переменную, должны указывать в своих вызовах pthread_cond_wait() (или pthread_cond_timedwait()) один и тот же мьютекс. В сущности, вызов pthread_cond_wait() на период своей работы динамически привязывает условную переменную к уникальному мьютексу. В стандарте SUSv3 отмечается, что результат использования более чем одного мьютекса в вызовах pthread_cond_wait(), конкурирующих за одну и ту же условную переменную, является неопределенным.
Собрав воедино вышеперечисленные детали, мы теперь можем отредактировать главный поток (потребитель) так, чтобы он использовал функцию pthread_cond_wait():
for (;;) {
s = pthread_mutex_lock(&mtx);
if (s!= 0)
errExitEN(s, "pthread_mutex_lock");
while (avail == 0) { /* Ждем элементов, которые можно потребить */
s = pthread_cond_wait(&cond, &mtx);
if (s!= 0)
errExitEN(s, "pthread_cond_wait");
}
while (avail > 0) { /* Потребляем все доступные элементы */
/* Делаем что-нибудь со сгенерированным элементом */
avail-;
}
s = pthread_mutex_unlock(&mtx);
if (s!= 0)
errExitEN(s, "pthread_mutex_unlock");
/* Возможно, выполняем другую работу, не требующую закрытия мьютекса */
}
Завершим этот подраздел последним наблюдением касательно использования функции pthread_cond_wait() (и pthread_cond_timedwait()). В коде потока, генерирующего элементы, делается вызов pthread_mutex_unlock() и затем pthread_cond_signal(); мы сначала открываем мьютекс, связанный с разделяемой переменной, после чего оповещаем соответствующую условную переменную. Эти два шага можно было бы поменять местами; стандарт SUSv3 позволяет выполнять их в произвольном порядке.
В книге [Butenhof, 1996] отмечается, что в некоторых реализациях открытие мьютекса и последующее уведомление условной переменной может обеспечивать лучшую производительность, чем если бы эти действия осуществлялись в обратном порядке. Если мьютекс открывается только после уведомления условной переменной, поток, выполняющий pthread_cond_wait(), может возобновить работу в момент, когда мьютекс все еще закрыт, и, обнаружив это, сразу же вернуться к состоянию ожидания. Это приводит к двум лишним переключениям контекста. Некоторые реализации устраняют эту проблему, используя методику называемую трансформацией ожидания, которая позволяет переместить поток с оповещением из очереди ожидания условной переменной в очередь ожидания мьютекса, не переключая при этом контекст (если мьютекс закрыт).
30.2.3. Проверка предиката условной переменной
У каждой условной переменной есть свой предикат на основе одной или нескольких разделяемых переменных. Например, в коде из предыдущего раздела предикатом, связанным с аргументом cond, было выражение (avail == 0). Этот участок кода демонстрирует общий принцип проектирования: вызов pthread_cond_wait() должен выполняться циклом while, а не инструкцией if. Дело в том, что при возврате из функции pthread_cond_wait() состояние предиката может быть произвольным; следовательно, мы должны сразу же его перепроверить и продолжить ожидание, если он не находится в нужном нам состоянии.
Предположения о состоянии предиката при возвращении из функции pthread_cond_wait() являются несостоятельными по следующим причинам.
•
30.2.4. Пример программы: подсоединение любого завершенного потока