Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Следующее, что видно из примера, — это то, что структура «потребителя» идентична таковой в предыдущем примере с ждущей блокировкой. Мы заменили функции pthread_sleepon_lock и pthread_sleepon_unlock на стандартные мутекс-ориентированные версии (pthread_mutex_lock и pthread_mutex_unlock). Функция pthread_sleepon_wait была заменена на функцию pthread_cond_wait.
Основное различие здесь состоит в том, что библиотека ждущих блокировок имеет скрытый внутренний мутекс, а при использовании
И, наконец, обратите внимание на то, что мы использовали функцию pthread_cond_signal вместо функции pthread_sleepon_signal (опять же, с явной передачей мутекса).
В разделе о ждущих блокировках мы обещали обсудить различие между функциями pthread_sleepon_broadcast и pthread_sleepon_signal. Заодно поговорим и о различии между двумя аналогичными функциями, имеющими отношение к условным переменным: pthread_cond_signal и pthread_cond_broadcast.
В двух словах, функция в варианте «signal» разблокирует только один поток. Например, если бы несколько потоков находилось в ожидании по функции «wait», и некий поток вызвал бы функцию pthread*_signal, то был бы разблокирован только один из ждущих потоков. Который из них? Тот, у которого наивысший приоритет. Если имеется два или более потоков с одинаковым приоритетом, порядок «пробуждения» будет не определен. Применение же варианта pthread*_broadcast приведет к тому что будут разблокированы все ожидающие потоки.
Разблокировать все потоки может показаться излишним. Но с другой стороны, разблокировать только один (причем случайный поток тоже не совсем корректно.
Поэтому мы должны думать, где имеет смысл использовать какой вариант. Очевидно, что если у вас только один ждущий поток, как у нас и было во всех вариантах «потребителя», функция pthread*_signal прекрасно справится — будет разблокирован один поток, и как раз тот, который нужно (потому что других просто нет).
В ситуации с несколькими потоками в первую очередь следует выяснить: а почему они ждут? Обычно на этот вопрос есть два ответа:
• все потоки рассматриваются как эквивалентные и реально образуют пул доступных потоков, готовых к обработке некоторого запроса;
• все потоки являются уникальными, и каждый из них ждет соблюдения своего специфического условия.
В первом случае мы можем представить себе, что код всех потоков имеет примерно следующий вид:
В этом случае абсолютно неважно, который именно из потоков получит данные — главное, чтобы хотя бы один сделал это и произвел над этими данными необходимые действия.
Однако, если ваш код подобен приведенному ниже, все будет несколько по-иному:
В этом случае пробуждение одного потока ничего не даст! Здесь мы обязаны «разбудить» все три потока, чтобы каждый из них проверил соблюдение своего условия.
Это в полной мере отражает второй вариант ответа на наш вопрос «а почему они ждут?» Так как все потоки все ждут соблюдения различных условий (поток thread1 ждет, пока значение x не станет меньше или равно 7, или пока значение у не станет равным 15, поток thread2 ждет, пока значение x не станет простым числом, а поток thread3 ждет, пока x не станет равным у), у нас нет никакого выбора, кроме как «разбудить» все потоки «одновременно».