QNX предоставляет упрощенный вариант использования условной переменной для блокирования (остановки) потока при помощи интерфейса так называемой
За этим интерфейсом на самом деле скрывается один мьютекс и несколько дополнительных условных переменных. Использование функций ожидания должно проходить внутри участка кода, отмеченного вызовами блокирования и разблокирования мьютекса, ассоциированного со ждущей блокировкой. Одним из основных недостатков ждущей блокировки является то, что для всех потоков и всех ключей ожидания используется один общий мьютекс. ОС не может никоим образом отслеживать взаимные блокировки потоков при использовании ждущих блокировок. В целом поведение этого средства синхронизации идентично бинарным семафорам, но оно требует дополнительных операций блокирования мьютекса.
Все функции для работы со ждущими блокировками объявлены в файле
.
Операции со ждущей блокировкой
Захват и освобождение ждущей блокировки
Вызов функций ожидания может производиться только внутри блока захвата и освобождения ждущей блокировки:
int pthread_sleepon_lock(void);
int pthread_sleepon_unlock(void);
Функция захвата
pthread_sleepon_lock
возвращает следующие значения:
EOK
— успешное выполнение;
EDEADLK
— попытка повторного захвата мьютекса;
EAGAIN
— может возникнуть при первом вызове в процессе, если системе не хватает ресурсов для создания внутреннего мьютекса.
Функция освобождения
pthread_sleepon_unlock
возвращает значения:
EOK
— успешное выполнение;
EPERM
— вызвавший поток не является владельцем внутреннего мьютекса.
Функции ожидания
Ожидание выполнения условия для ждущей блокировки может выполняться в двух вариантах: простое ожидание и ожидание с установкой тайм-аута.
int pthread_sleepon_wait(const volatile void* addr);
int pthread_sleepon_timedwait(const volatile void* addr, uint64_t nsec);
При вызове функций ожидания необходимо указать ключ
addr
(произвольный адрес в памяти). Если этот адрес указывается впервые, для данного вызова создается новая условная переменная. Поток освобождает захваченный внутренний мьютекс и переходит в состояние блокировки на условной переменной.
Ожидание завершения потока
Ожидание родительским потоком завершения одного или нескольких порожденных им «присоединенных» потоков (на вызове
pthread_join
) — это простейший и эффективный вариант синхронизации потоков, не требующий для своей реализации каких-либо дополнительных синхронизирующих примитивов. Ранее мы уже детально рассматривали процесс порождения и ожидания завершения потоков, сейчас же лишь коротко вернемся к этому вопросу с иной точки зрения - с позиции синхронизации. В простейшем случае общая схема такой синхронизации всегда одинакова и описывается подобной структурой кода:
void* threadfunc(void* data) {
...
return NULL;
}
...
// здесь создается нужное количество (N) потоков:
pthread_t tid[N];
for (int i = 0; i < N; i++)
pthread_create(tid + 1, NULL, threadfunc, NULL);
// а вот здесь ожидается завершение
всехпотоков!
for (int i = 0; i < N; i++)
pthread_join(tid + 1, NULL);
При первом знакомстве с подобным шаблоном кода пугает то обстоятельство, что предписан такой же порядок ожидания завершения потоков, как и при их создании. И это при том, что порядок их завершения может быть совершенно произвольным. Но представленный шаблон верен: если некоторый ожидаемый в текущем цикле поток
j
«задерживается», а мы заблокированы именно в ожидании
tid[j]
, то после завершения этого ожидаемого потока, которое когда-то все-таки наступит, мы «мгновенно» пробегаем все последующие
i
, для которых соответствующие
tid[i]
уже завершились ранее. Так что представленный шаблон корректен и широко используется на практике.