Потоки управления
В рамках любого процесса существует по крайней мере один поток управления. Потоки в пределах процесса именуются идентификаторами. Для выяснения собственного идентификатора поток может воспользоваться функцией pthread_self().
Порождению потоков управления обычно предшествует создание атрибутного объекта, осуществляемое функцией pthread_attr_init(). Когда объект перестает быть нужным, его ликвидируют, вызывая pthread_attr_destroy().
Атрибутные объекты трактуются стандартом POSIX-2001 как абстрактные. Их структура скрыта от приложений, а все манипуляции выполняются посредством методов для выборки и изменения атрибутов.
В число поддерживаемых входят атрибуты стека (функции pthread_attr_getstack(), pthread_attr_setstack(), pthread_attr_getguardsize(), pthread_attr_setguardsize()), планирования (pthread_attr_getschedparam(), pthread_attr_setschedparam(), pthread_attr_getschedpolicy(), pthread_attr_setschedpolicy(), pthread_attr_getscope(), pthread_attr_setscope(), pthread_attr_getinheritsched(), pthread_attr_setinheritsched()) и обособленности (pthread_attr_getdetachstate(), pthread_attr_setdetachstate()).
Обратим внимание на средство контроля переполнения стека – защитную область, располагающуюся за верхней границей стека. При переполнении и попадании указателя стека в защитную область операционная система должна фиксировать ошибку.
Значения атрибутов планирования могут задаваться не только при создании потока управления. Стандарт POSIX-2001 предоставляет средства для их динамического изменения и опроса – функции pthread_getschedparam(), pthread_setschedparam(), pthread_setschedprio(), pthread_getconcurrency(), pthread_setconcurrency().
К числу атрибутов потока управления можно отнести обслуживающие его часы процессорного времени. Для выяснения их идентификатора достаточно обратиться к функции pthread_getcpuclockid().
Еще один атрибут потока управления – маска блокированных сигналов. Поток может опросить и/или изменить ее посредством вызова функции pthread_sigmask().
Все потоки управления одного процесса разделяют общее адресное пространство и, следовательно, имеют общие данные.
Чтобы сделать некоторые данные индивидуальными для потока, нужно с помощью функции pthread_key_create() создать ключ и ассоциировать с ним индивидуальные данные, воспользовавшись функцией pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому) ключу разные потоки будут получать доступ к разным данным.
Создать один ключ, очевидно, нужно один раз. Для решения проблемы однократного выполнения инициализирующих действий в многопотоковой среде стандарт POSIX-2001 предлагает функцию pthread_once().
За удаление ключа индивидуальных данных потоков управления отвечает функция pthread_key_delete().
Модель порождения потоков управления отличается от соответствующей модели для процессов. При создании нового потока задается функция, с вызова которой начнется его выполнение, то есть вместо пары вида fork()/exec() создающий поток должен обратиться лишь к одной функции – pthread_create().
От "родительского" вновь созданный поток управления наследует маску сигналов и среду вещественной арифметики.
К числу средств создания потоков можно отнести и функцию fork(). С ней можно ассоциировать обработчики, зарегистрировав их с помощью функции pthread_atfork(). В каждом обращении к pthread_atfork() фигурируют три обработчика. Первый выполняется в контексте потока, вызвавшего fork(), до разветвления процесса; второй – в том же контексте, но после разветвления; третий – в контексте единственного потока порожденного процесса.
поток управления можно терминировать изнутри и извне (из других потоков того же процесса). Обычное средство внутреннего терминирования – выход из стартовой функции. Тот же эффект достигается вызовом функции pthread_exit().
Заказать терминирование извне потока управления с заданным идентификатором можно, воспользовавшись функцией pthread_cancel().
На выполнение "заказа" влияют состояние восприимчивости к терминированию (разрешено/запрещено) и тип терминирования (отложенное или немедленное, асинхронное), установленные для потока, а также достижение точки терминирования.
Эти атрибуты опрашиваются и изменяются с помощью функций pthread_setcancelstate(), pthread_setcanceltype() и pthread_testcancel().
Стандартом POSIX- 2001 предусмотрено существование стека обработчиков завершения, ассоциированного с потоком управления. Операции над этим стеком возложены на функции pthread_cleanup_push() и pthread_cleanup_pop().
После того как выполнятся все обработчики завершения, в неспецифицированном порядке вызываются деструкторы индивидуальных данных.
Возможность дождаться завершения заданного потока управления реализуется функцией pthread_join().
Помимо заказа на терминирование, потоку управления можно направить сигнал, воспользовавшись функцией pthread_kill().
Полезная операция, связанная с обработкой завершения потока управления, – его динамическое обособление, выполняемое функцией pthread_detach().
Обратим внимание на то, что, согласно стандарту POSIX-2001, функции, обслуживающие потоки управления, никогда не завершаются с частичным результатом и не выдают код ошибки EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.