Шрифт:
Здесь мы можем столкнуться с новой проблемой. Как «потребителю» сбрасывать флаг data_ready согласованно с «производителем»? Очевидно, нам понадобится некоторая форма монопольного доступа к флагу, чтобы в любой момент времени только один из этих потоков мог модифицировать его. Метод, который применен в данном случае, заключается в применения мутекса, но это внутренний мутекс библиотеки ждущих блокировок, так что мы сможем обращаться к нему только с помощью двух функций: pthread_sleepon_lock и pthread_sleepon_unlock. Давайте модифицируем наш поток-«потребитель»:
Здесь мы добавили «потребителю» установку и снятие блокировки. Это означает, что потребитель может теперь надежно проверять флаг data_ready, не опасаясь гонок, а также надежно его устанавливать.
Великолепно! А как насчет собственно процесса ожидания? Как мы и предполагали ранее, там действительно применяется вызов функции pthread_sleepon_wait. Вот второй while-цикл:
Функция pthread_sleepon_wait в действительности выполняет три действия:
1. Разблокирует мутекс библиотеки ждущих блокировок.
2. Выполняет собственно операцию ожидания.
3. Снова блокирует мутекс библиотеки ждущих блокировок.
Причина обязательной разблокировки/блокировки мутекса библиотеки проста: поскольку суть мутекса состоит в обеспечении взаимного исключения доступа к флагу data_ready, мы хотим запретить потоку-«производителю» изменять флаг data_ready, пока мы его проверяем. Но если мы не разблокируем флаг впоследствии, то поток-«производитель» не сможет его установить, чтобы сообщить нам о доступности данных! Операция повторной блокировки выполняется автоматически исключительно для удобства, чтобы вызвавший функцию pthread_sleepon_wait поток не беспокоился о состоянии блокировки после «пробуждения».
Давайте перейдем теперь к потоку-«производителю» и рассмотрим, как он использует библиотеку ждущих блокировок. Вот его полная реализация:
Как вы видите, поток-«производитель» также блокирует мутекс, чтобы получить монопольный доступ к флагу data_ready перед его установкой.
Давайте рассмотрим происходящее в подробностях. Определим состояния «потребителя» и «производителя» следующим образом:
Состояние | Означает |
---|---|
CONDVAR | ожидание соответствующей ждущей блокировке условной переменной |
MUTEX | ожидание мутекса |
READY | состояние готовности, т.е., готов выполняться или уже выполняется |
INTERRUPT | ожидание прерывания от аппаратных средств |
Действие | Владелец мутекса | Состояние «потребителя» | Состояние «производителя» |
---|---|---|---|
«потребитель» блокирует мутекс | «потребитель» | READY | INTERRUPT |
«потребитель» проверяет флаг data_ready | «потребитель» | READY | INTERRUPT |
потребитель вызывает функцию pthread_sleepon_wait | «потребитель» | READY | INTERRUPT |
функция pthread_sleepon_wait разблокирует мутекс | мутекс свободен | READY | INTERRUPT |
функция pthread_sleepon_wait блокируется | мутекс свободен | CONDVAR | INTERRUPT |
пауза до прерывания | мутекс свободен | CONDVAR | INTERRUPT |
аппаратные средства генерируют данные | мутекс свободен | CONDVAR | READY |
«производитель» блокирует мутекс | «производитель» | CONDVAR | READY |
«производитель» устанавливает флаг data_ready | «производитель» | CONDVAR | READY |
«производитель» вызывает pthread_sleepon_signal | «производитель» | CONDVAR | READY |
«потребитель» «пробуждается», функция pthread_sleepon_wait пытается заблокировать мутекс | «производитель» | MUTEX | READY |
«производитель» разблокирует мутекс | мутекс свободен | MUTEX | READY |
«потребитель» получает мутекс | «потребитель» | READY | READY |
«потребитель» обрабатывает данные | «потребитель» | READY | READY |
«производитель» ждет новых данных от аппаратуры | «потребитель» | READY | INTERRUPT |
пауза («потребитель» обрабатывает полученные данные) | «потребитель» | READY | INTERRUPT |
«потребитель» завершает обработку и разблокирует мутекс | мутекс свободен | READY | INTERRUPT |
«потребитель» возвращается в начало цикла и блокирует мутекс | «потребитель» | READY | INTERRUPT |
Последняя строка в таблице повторяет первую — мы совершили один полный цикл.
Каково назначение флага data_ready? Он служит для двух целей:
• Он является флагом состояния — посредником между «потребителем» и «производителем», указывающим на состояние системы. Если флаг установлен в состояние 1, это означает, что данные доступны для обработки; если этот флаг установлено в состояние 0, это означает, что данных нет, и поток-потребитель должен быть заблокирован.