Последующие расширения [14] Часто в публикациях ссылаются на расширения реального времени POSIX 1003b (1993). Но POSIX 1003b не описывают группу pthread_* , хотя именно в этой редакции определены семафоры sem_t . У. Стивенс [2] указывает, что программные потоки POSIX определены в редакции 1003.1 (1995).
POSIX специфицируют широкий спектр механизмов «легких процессов» — потоков (группа API pthread_*()
). Техника потоков вводит новую парадигму программирования вместо уже ставших традиционными UNIX-методов. Это обстоятельство часто недооценивается. Например, использование pthread_create()
вместо fork()
может на порядки повысить скорость реакций, особенно в ОС с отсутствием механизмов COW (copy on write) при создании дубликатов физических страниц RAM сегментов данных (таких как QNX, хотя механизмы COW вряд ли вообще применимы в ОС реального времени) [4]. Другой пример: использование множественных потоков вместо ожиданий на множестве дескрипторов в операторе select()
.
Однако очень часто эти две парадигмы, традиционная и потоковая, не сочетаются в рамках единого кода из-за небезопасности (not thread safe) традиционных механизмов UNIX ( fork()
, select()
и др.) в многопоточной среде. Тогда приходится использовать либо одну, либо другую парадигму как альтернативы, не смешивая их между собой. Или смешивать, но с большой осторожностью и с хорошим пониманием того, что при этом может произойти в каждом случае.
Поток можно понимать как любой автономный последовательный (линейный) набор команд процессора. Источником этого линейного кода для потока могут служить:
• бинарный исполняемый файл, на основе которого системой или вызовом группы spawn()
запускается новый процесс и создается его главныйпоток;
• дубликат кода главного потока [15] Клонирование многопоточных процессов с помощью fork() — это отдельная песня. Хотя POSIX и предусматривает реализацию ( pthread_atfork() ) такой возможности, до конца не ясно, как это работает. API QNX предоставляет эту возможность, но предупреждает, что пользователь сам отвечает за последствия. Детали механизма pthread_atfork() см. в справочном руководстве QNX.
процесса родителя при клонировании процессов вызовом fork()
(тоже относительно главного потока);
• участок кода, оформленный функцией специального типа ( void*()(void*)
); это общий случай при создании второго и всех последующих потоков процесса (при создании многопоточных процессов) вызовом pthread_create()
. Такую функцию мы будем называть функцией потока. Это наиболее интересный для нас случай.
В первых двух вариантах мы имеем неявное создание (главного) потока и, как следствие, порождение нового процесса. В последнем случае - явное создание потока, которое в литературе, собственно, и именуется «созданием потока». Хотя сущность происходящего относительно исполняющегося потока во всех случаях все же остается неизменной.
Кроме последовательности команд к потоку нужно отнести и те локальные данные, с которыми работает функция потока, то есть собственный стек потока. Во время приостановки системой выполнения (диспетчеризации) кода текущего потока должна обеспечиваться возможность сохранения текущих значений регистров (включая регистры FPU, сегментные регистры) и, возможно, другой специфической информации. Текущее значение этого набора данных, относящихся к выполнению текущего потока, называется контекстом потока. Контекст потока, кроме того, обеспечивает связь потока с его экземпляром собственных данных, о чем мы детально поговорим чуть позже. Детальная структура и объем данных, составляющих контекст потока, определяются не только самой ОС, но и типом процессорной архитектуры, на которой она выполняется (для многоплатформенных ОС, к которым принадлежит и QNX).
В принципе считается, что время переключения контекстов потоков в пределах одного процесса и время переключения контекстов процессов могут заметно отличаться, особенно для платформ с управлением виртуальной памятью. [16] Собственно с этим и связано употребление применительно к потокам названия «легкие процессы». Впервые этот термин (LWP — lightweight process) ввела в своей технической документации для обозначения понятия, эквивалентного потоку, фирма Sun Microsystems.
Однако удобства реализации и стремление к однородности могут перевесить соблазн разработчиков ОС использовать это различие, что мы вскоре и увидим в отношении QNX.
Идентификатором потока (значимым только внутри одного процесса!) является TID (Thread ID), присваиваемый потоку при его создании вызовом pthread_create()
. TID позволяет процессу (а также системе в рамках процесса) однозначно идентифицировать каждый поток. Нумерация TID в QNX начинается с 1 (это всегда главный поток процесса, порожденный main()
) и последовательно возрастает по мере создания потоков (до 32767). [17] Эта схема PID/TID описана в POSIX, но выполняется далеко не во всех UNIX-совместимых ОС. Например, вплоть до самых последних редакций ядра Linux (ситуация стала меняться только сейчас) процессы ( fork() ) и потоки ( pthread_create() ) (создавались на базе единого системного вызова ( _clone() ) и TID являлись идентификаторами в едином ряду PID. Это может привести к трудно выявляемым ошибкам при переносе программ между двумя ОС.
Читать дальше
Конец ознакомительного отрывка
Купить книгу