Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform | страница 52
Ждущие блокировки
Другая типовая ситуация в многопоточных программах — это потребность заставить поток «ждать чего-либо». Этим «чем- либо» может являться фактически что угодно! Например, когда доступны данные от устройства, или когда конвейерная лента находится в нужной позиции, или когда данные сохранены на диск, и т.д. Еще одна хитрость этой ситуации состоит в том, что одного и того же события могут ожидать несколько потоков.
Для таких целей мы могли бы использовать либо условную переменную (condition variable), о которой речь ниже, либо, что гораздо проще, ждущую блокировку (sleepon).
Для применения ждущих блокировок надо выполнить несколько операций. Рассмотрим сначала вызовы, а затем вернемся к использованию ждущих блокировок.
>int pthread_sleepon_lock(void);
>int pthread_sleepon_unlock(void);
>int pthread_sleepon_broadcast(void *addr);
>int pthread_sleepon_signal(void *addr);
>int pthread_sleepon_wait(void *addr);
Как было отмечено ранее, потоку может быть необходимо ждать какого-нибудь события. Наиболее очевидный выбор из представленного выше списка функций — это функция pthread_sleepon_wait(). Но сначала поток должен проверить, надо ли ждать. Давайте приведем пример. Один поток представляет собой поток-«поставщик», который получает данные от неких аппаратных средств. Другой поток — поток-«потребитель» и он неким образом обрабатывает поступающие данные. Рассмотрим сначала поток-«потребитель»:
>volatile int data_ready = 0;
>consumer() {
> while (1) {
> while (!data_ready) {
> // wait
> }
> // Обработать данные
> }
>}
«Потребитель» вечно находится в своем главном обрабатывающем цикле (>while(1)
). Первое, что он проверяет — это флаг data_ready. Если этот флаг равен 0, это означает, что данных нет, и их надо ждать. Впоследствии поток-«производитель» должен будет как-то «разбудить» его, и тогда поток-«потребитель» должен будет повторно проверить состояние флага data_ready. Положим, что происходит именно это. Поток-«потребитель» анализирует состояние флага и определяет, что флаг равен 1, то есть данные теперь доступны. Поток-«потребитель» переходит к обработке поступивших данных, после чего он должен снова проверить, не поступили ли новые данные, и так далее.
Здесь мы можем столкнуться с новой проблемой. Как «потребителю» сбрасывать флаг data_ready согласованно с «производителем»? Очевидно, нам понадобится некоторая форма монопольного доступа к флагу, чтобы в любой момент времени только один из этих потоков мог модифицировать его. Метод, который применен в данном случае, заключается в применения мутекса, но это внутренний мутекс библиотеки ждущих блокировок, так что мы сможем обращаться к нему только с помощью двух функций: