Литмир - Электронная Библиотека
Содержание  
A
A

 exit(EXIT_SUCCESS);

}

Два одновременно выполняющихся процесса настолько симметричны и идентичны, что они даже не анализируют PID после выполнения

fork()
, они только в максимальном темпе «перепасовывают» друг другу активность, как волейболисты делают это с мячом (рис. 2.2).

QNX/UNIX: Анатомия параллелизма - img_3.png

Рис. 2.2. Симметричное взаимодействие потоков

Рисунок 2.2 иллюстрирует взаимодействие двух идентичных процессов: вся их «работа» состоит лишь в том, чтобы как можно быстрее передать управление партнеру. Такую схему, когда два и более как можно более идентичных потоков или процессов в максимально высоком темпе (на порядок превосходящем последовательность «естественной» RR-диспетчеризации) обмениваются активностью, мы будем неоднократно использовать в дальнейшем для различных механизмов, называя ее для простоты «симметричной схемой».

Примечание

Чтобы максимально упростить код приложения, при его написании мы не трогали события «естественной» диспетчеризации, имеющие место при RR-диспетчеризации каждые 4 системных тика (по умолчанию это ~4 миллисекунды). Как сейчас покажут результаты, события принудительной диспетчеризации происходят с периодичностью порядка 1 микросекунды, т.e. в 4000 раз чаще, и возмущения, возможно вносимые RR-диспетчеризацией, можно считать не настолько существенными.

Вот результаты выполнения этой программы:

# nice -n-19 p5 1000000

1069102 : cycles - 1234175656; on sched — 617

0       : cycles - 1234176052; on sched - 617

# nice -n-19 p5 100000

1003566 : cycles - 123439225; on sched — 617

0       : cycles - 123440347; on sched - 617

# nice -n-19 p5 10000

1019950 : cycles - 12339084; on sched — 616

0       : cycles - 12341520; on sched - 617

# nice -n-19 p5 1000

1036334 : cycles - 1243117; on sched — 621

0       : cycles - 1245123; on sched - 622

# nice -n-19 p5 100

1052718 : cycles - 130740; on sched — 653

0       : cycles - 132615; on sched - 663

Видна на удивление устойчивая оценка, практически не зависящая от общего числа актов диспетчеризации, изменяющегося на 4 порядка.

Отбросив мелкие добавки, привносимые инкрементом и проверкой счетчика цикла, можно считать, что передача управления от процесса к процессу требует порядка 600 циклов процессора (это порядка 1,2 микросекунды на компьютере 533 МГц, на котором выполнялся этот тест).

Потоки

Последующие расширения [14]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()
(тоже относительно главного потока);

• участок кода, оформленный функцией специального типа (

void*()(void*)
); это общий случай при создании второго и всех последующих потоков процесса (при создании многопоточных процессов) вызовом
pthread_create()
. Такую функцию мы будем называть функцией потока. Это наиболее интересный для нас случай.

В первых двух вариантах мы имеем неявное создание (главного) потока и, как следствие, порождение нового процесса. В последнем случае - явное создание потока, которое в литературе, собственно, и именуется «созданием потока». Хотя сущность происходящего относительно исполняющегося потока во всех случаях все же остается неизменной.

Кроме последовательности команд к потоку нужно отнести и те локальные данные, с которыми работает функция потока, то есть собственный стек потока. Во время приостановки системой выполнения (диспетчеризации) кода текущего потока должна обеспечиваться возможность сохранения текущих значений регистров (включая регистры FPU, сегментные регистры) и, возможно, другой специфической информации. Текущее значение этого набора данных, относящихся к выполнению текущего потока, называется контекстом потока. Контекст потока, кроме того, обеспечивает связь потока с его экземпляром собственных данных, о чем мы детально поговорим чуть позже. Детальная структура и объем данных, составляющих контекст потока, определяются не только самой ОС, но и типом процессорной архитектуры, на которой она выполняется (для многоплатформенных ОС, к которым принадлежит и QNX).

В принципе считается, что время переключения контекстов потоков в пределах одного процесса и время переключения контекстов процессов могут заметно отличаться, особенно для платформ с управлением виртуальной памятью. [16]Однако удобства реализации и стремление к однородности могут перевесить соблазн разработчиков ОС использовать это различие, что мы вскоре и увидим в отношении QNX.

Идентификатором потока (значимым только внутри одного процесса!) является TID (Thread ID), присваиваемый потоку при его создании вызовом

pthread_create()
. TID позволяет процессу (а также системе в рамках процесса) однозначно идентифицировать каждый поток. Нумерация TID в QNX начинается с 1 (это всегда главный поток процесса, порожденный
main()
) и последовательно возрастает по мере создания потоков (до 32767). [17]

вернуться

14

Часто в публикациях ссылаются на расширения реального времени POSIX 1003b (1993). Но POSIX 1003b не описывают группу

pthread_*
, хотя именно в этой редакции определены семафоры
sem_t
. У. Стивенс [2] указывает, что программные потоки POSIX определены в редакции 1003.1 (1995).

вернуться

15

Клонирование многопоточных процессов с помощью

fork()
— это отдельная песня. Хотя POSIX и предусматривает реализацию (
pthread_atfork()
) такой возможности, до конца не ясно, как это работает. API QNX предоставляет эту возможность, но предупреждает, что пользователь сам отвечает за последствия. Детали механизма
pthread_atfork()
см. в справочном руководстве QNX.

вернуться

16

Собственно с этим и связано употребление применительно к потокам названия «легкие процессы». Впервые этот термин (LWP — lightweight process) ввела в своей технической документации для обозначения понятия, эквивалентного потоку, фирма Sun Microsystems.

вернуться

17

Эта схема PID/TID описана в POSIX, но выполняется далеко не во всех UNIX-совместимых ОС. Например, вплоть до самых последних редакций ядра Linux (ситуация стала меняться только сейчас) процессы (

fork()
) и потоки (
pthread_create()
) (создавались на базе единого системного вызова (
_clone()
) и TID являлись идентификаторами в едином ряду PID. Это может привести к трудно выявляемым ошибкам при переносе программ между двумя ОС.

14
{"b":"155449","o":1}