exit(EXIT_SUCCESS);
}
Два одновременно выполняющихся процесса настолько симметричны и идентичны, что они даже не анализируют PID после выполнения
fork()
![QNX/UNIX: Анатомия параллелизма img_3.png](https://litlife.club/books/155449/read/images/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_*()
pthread_create()
fork()
select()
Однако очень часто эти две парадигмы, традиционная и потоковая, не сочетаются в рамках единого кода из-за небезопасности (not thread safe) традиционных механизмов UNIX (
fork()
select()
Поток можно понимать как любой автономный последовательный (линейный) набор команд процессора. Источником этого линейного кода для потока могут служить:
• бинарный исполняемый файл, на основе которого системой или вызовом группы
spawn()
• дубликат кода главного потока [15]процесса родителя при клонировании процессов вызовом
fork()
• участок кода, оформленный функцией специального типа (
void*()(void*)
pthread_create()
В первых двух вариантах мы имеем неявное создание (главного) потока и, как следствие, порождение нового процесса. В последнем случае - явное создание потока, которое в литературе, собственно, и именуется «созданием потока». Хотя сущность происходящего относительно исполняющегося потока во всех случаях все же остается неизменной.
Кроме последовательности команд к потоку нужно отнести и те локальные данные, с которыми работает функция потока, то есть собственный стек потока. Во время приостановки системой выполнения (диспетчеризации) кода текущего потока должна обеспечиваться возможность сохранения текущих значений регистров (включая регистры FPU, сегментные регистры) и, возможно, другой специфической информации. Текущее значение этого набора данных, относящихся к выполнению текущего потока, называется контекстом потока. Контекст потока, кроме того, обеспечивает связь потока с его экземпляром собственных данных, о чем мы детально поговорим чуть позже. Детальная структура и объем данных, составляющих контекст потока, определяются не только самой ОС, но и типом процессорной архитектуры, на которой она выполняется (для многоплатформенных ОС, к которым принадлежит и QNX).
В принципе считается, что время переключения контекстов потоков в пределах одного процесса и время переключения контекстов процессов могут заметно отличаться, особенно для платформ с управлением виртуальной памятью. [16]Однако удобства реализации и стремление к однородности могут перевесить соблазн разработчиков ОС использовать это различие, что мы вскоре и увидим в отношении QNX.
Идентификатором потока (значимым только внутри одного процесса!) является TID (Thread ID), присваиваемый потоку при его создании вызовом
pthread_create()
main()
14
Часто в публикациях ссылаются на расширения реального времени POSIX 1003b (1993). Но POSIX 1003b не описывают группу
pthread_*
sem_t
15
Клонирование многопоточных процессов с помощью
fork()
pthread_atfork()
pthread_atfork()
16
Собственно с этим и связано употребление применительно к потокам названия «легкие процессы». Впервые этот термин (LWP — lightweight process) ввела в своей технической документации для обозначения понятия, эквивалентного потоку, фирма Sun Microsystems.
17
Эта схема PID/TID описана в POSIX, но выполняется далеко не во всех UNIX-совместимых ОС. Например, вплоть до самых последних редакций ядра Linux (ситуация стала меняться только сейчас) процессы (
fork()
pthread_create()
_clone()