pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_create(NULL, &attr, &func, NULL);
Как и при установке политики диспетчеризации, параметры диспетчеризации потока (и приоритет в их составе) будут установлены, только если параметр типа наследования от родителя установлен в PTHREAD_EXPLICIT_SCHED
посредством вызова pthread_attr_setinheritsched()
.
Заметим здесь вскользь (в дальнейшем нам представится возможность использовать эти знания), что помимо «продуктивных» потоков (компонент системы и пользовательских приложений) в системе всегда существует один «паразитный» поток, запущенный с приоритетом 0 (idle-поток). Он «выбирает» весь остаток процессорного времени в те периоды, когда все имеющиеся в системе продуктивные потоки перейдут в блокированные состояния (ожидания). Подобная практика хорошо известна и реализуется также в большинстве других операционных систем.
Отличия от POSIX
Если следовать POSIX-стандарту, то некоторые из атрибутов невозможно переопределить до фактического создания этого стандарта (их можно изменить позже в самом коде потока, но иногда это не совсем правильное решение). Все эти возможности относятся к асинхронному завершению потока; детали функционирования этого механизма рассматриваются позже. К подобного рода атрибутам относятся:
• запретить асинхронное завершение (отмену) потока;
• установить тип завершаемости потока;
• определить, что должно происходить при доставке потоку сигналов.
QNX расширяет возможности POSIX, позволяя по условию OR установить соответствующие биты- флаги в поле flags
атрибутной записи, прежде чем будет произведен вызов, создающий поток. Не существует функций вида pthread_attr_set_*()
, эквивалентных этим установкам. К этим флагам относятся:
• PTHREAD_CANCEL_ENABLE
— запрос на завершение будет обрабатываться в соответствии с типом завершаемости, установленным для потока (значение по умолчанию);
• PTHREAD_CANCEL_DISABLE
— запросы на завершение будут отложены;
• PTHREAD_CANCEL_ASYNCHRONOUS
— если завершение разрешено, отложенные или текущие запросы будут выполнены немедленно;
• PTHREAD_CANCEL_DEFERRED
— если завершение разрешено, запросы на завершение будут отложены до достижения точки завершаемости (значение по умолчанию);
• PTHREAD_MULTISIG_ALLOW
— завершать по сигналу все потоки в процессе (POSIX-умолчание);
• PTHREAD_MULTISIG_DISALLOW
— завершать по сигналу только тот поток, который принял сигнал.
После запуска потока все атрибуты, связанные с завершаемостью потока, могут быть изменены вызовами pthread_setcancelstate()
и pthread_setcanceltype()
.
Передача параметров потоку
Зачастую каждый поток из группы последовательно создаваемых потоков, выполняющих одну и ту же функцию, нужно запускать со своим индивидуальным блоком данных (параметром потока). Для этого предназначен 4-й параметр вызова pthread_create()
— указатель на блок данных типа void*
. Характерно, что это может быть произвольная структура данных сколь угодно сложного типа, структуризацию которой вызывающий pthread_create()
код и функция потока должны понимать единообразно; никакого контроля соответствия типов на этапе вызова не производится.
Достаточно часто встречающийся на практике образец многопоточного кода — это циклическая процедура ожидания наступления некоторого условия (события), после которого порождается новый экземпляр потока, призванный обслужить наступившее событие (типичная схема всего разнообразия многопоточных сетевых серверов). В таких случаях код, порождающий потоки, выглядит подобно следующему фрагменту:
// функция потока:
void* ThreadProc(void* data) {
// ... выполняется обработка, используя структуру *(DataParam*)data
return NULL;
}
// порождающий потоки код:
while (true) {
// инициализация области параметров
struct DataParam data(...);
if ( /* ожидаем нечто */ )
pthread_create(NULL, &attr, &ThreadProc, &data);
}
Этот простейший код крайне опасен: при быстрых циклах и, что намного важнее, непредсказуемых моментах повторных созданий экземпляров потоков из вызывающего цикла необходимо обеспечить, чтобы используемое в функции потока ThreadProc()
значение данных было адекватным. Оно может быть изменено в вызывающем коде или даже, более того, просто разрушено при выходе из локальной области видимости, как в следующем коде:
// порождающий потоки код:
while(true) {
if ( /* ожидаем нечто */ ) {
struct DataParam data(...);
pthread_create(NULL, &attr, &ThreadProc, &data);
}
// здесь может идти достаточно длительная обработка
}
Здесь блок данных, выделяемый в стеке порождающего потока, к началу его использования в дочернем потоке может быть просто уничтожен.
Единственно надежный способ обеспечить требование актуальности передаваемых данных - это создание копии блока параметров непосредственно при входе в функцию потока, например так (если определена операция копирования):
// функция потока:
void* ThreadProc(void* data) {
DataParam copy = *(DataParam*)data;
// выполняется обработка, используя структуру copy
return NULL;
}