Категории средств обмена информацией
Процессы могут взаимодействовать друг с другом только обмениваясь информацией. По объему передаваемой информации и степени возможного воздействия на поведение другого процесса все средства такого обмена можно разделить на три категории:
· Сигнальные. Передается минимальное количество информации — один бит, “да” или “нет”. Используются, как правило, для извещения процесса о наступлении какого-либо события. Степень воздействия на поведение процесса, получившего информацию, минимальна. Все зависит от того, знает ли он, что означает полученный сигнал, надо ли на него реагировать и каким образом. Неправильная реакция на сигнал или его игнорирование могут привести к трагическим последствиям. Вспомним профессора Плейшнера из кинофильма “Семнадцать мгновений весны”. Сигнал тревоги — цветочный горшок на подоконнике — был ему передан, но проигнорирован. И к чему это привело?
· Канальные. Общение процессов происходит через линии связи, предоставленные операционной системой, и напоминает общение людей по телефону, с помощью записок, писем или объявлений. Объем передаваемой информации в единицу времени ограничен пропускной способностью линий связи. С увеличением количества информации увеличивается и возможность влияния на поведение другого процесса.
· Разделяемая память. Два или более процессов могут совместно использовать некоторую область адресного пространства. Созданием разделяемой памяти занимается операционная система (если, конечно, ее об этом попросят). Общение процессов напоминает совместное проживание студентов в одной комнате общежития. Возможность обмена информацией максимальна, как, впрочем, и влияние на поведение другого процесса, но требует повышенной осторожности (если вы переложили с места на место все вещи вашего соседа по комнате, а часть из них еще и выбросили, то представляете, как он отреагирует?). Использование разделяемой памяти для передачи/получения информации осуществляется с помощью средств обычных языков программирования, в то время как сигнальным и канальным средствам коммуникации для этого необходимы специальные системные вызовы. Разделяемая память представляет собой наиболее быстрый способ взаимодействия процессов в одной вычислительной системе.
Потоки исполнения
Усилия, направленные на ускорение решения задач в рамках классических операционных систем, привели к появлению совершенно иных механизмов, к изменению самого понятия “процесс”.
В свое время внедрение идеи мультипрограммирования позволило повысить пропускную способность компьютерных систем, т.е. уменьшить среднее время ожидания результатов работы процессов. Но любой отдельно взятый процесс в мультипрограммной системе никогда не может быть выполнен быстрее, чем при выполнении в однопрограммном режиме на том же вычислительном комплексе. Тем не менее, если алгоритм решения задачи обладает определенным внутренним параллелизмом, мы могли бы ускорить его работу, организовав взаимодействие нескольких процессов. Рассмотрим следующий пример. Пусть у нас есть следующая программа на псевдоязыке программирования:
Ввести массив a |
Ввести массив b |
Ввести массив c |
a = a + b |
c = a + c |
Вывести массив c |
При выполнении такой программы в рамках одного процесса этот процесс четырежды будет блокироваться, ожидая окончания операций ввода-вывода. Но наш алгоритм обладает внутренним параллелизмом. Вычисление суммы массивов a + b можно было бы делать параллельно с ожиданием окончания операции ввода массива c.
Ввести массив a | |
Ожидание окончания операции ввода | |
Ввести массив b | |
Ожидание окончания операции ввода | |
Ввести массив с | |
Ожидание окончания операции ввода | a = a + b |
c = a + c | |
Вывести массив с | |
Ожидание окончания операции вывода |
Такое совмещение операций по времени можно было бы реализовать, используя два взаимодействующих процесса. Для простоты будем полагать, что средством коммуникации между ними служит разделяемая память. Тогда наши процессы могут выглядеть следующим образом:
Процесс 1 | Процесс 2 | |
Ввести массив a | Ожидание ввода | |
Ожидание окончания операции ввода | массивов a и b | |
Ввести массив b | ||
Ожидание окончания операции ввода | ||
Ввести массив с | ||
Ожидание окончания операции ввода | a = a + b | |
c = a + c | ||
Вывести массив с | ||
Ожидание окончания операции вывода |
Казалось бы, мы предложили конкретный способ ускорения решения задачи. Однако в действительности дело обстоит не так просто. Второй процесс должен быть создан, оба процесса должны сказать операционной системе, что им необходима память, которую они могли бы разделить с другим процессом, и, наконец, нельзя забывать о переключении контекста. Поэтому реальное поведение процессов будет выглядеть примерно так.
Процесс 1 | Процесс 2 | |
Создать процесс 2 | ||
Переключение контекста | ||
Выделение общей памяти | ||
Ожидание ввода a и b | ||
Переключение контекста | ||
Выделение общей памяти | ||
Ввести массив a | ||
Ожидание окончания операции ввода | ||
Ввести массив b | ||
Ожидание окончания операции ввода | ||
Ввести массив с | ||
Ожидание окончания операции ввода | ||
Переключение контекста | ||
a = a + b | ||
Переключение контекста | ||
c = a + c | ||
Вывести массив с | ||
Ожидание окончания операции вывода |
Как видим, мы можем не только не выиграть во времени решения задачи, но даже и проиграть, так как временные потери на создание процесса, выделение общей памяти и переключение контекста могут превысить выигрыш, полученный за счет совмещения операций.
Для того, чтобы реализовать нашу идею, введем новую абстракцию внутри понятия “процесс” – поток исполнения или просто поток (в англоязычной литературе используется термин thread). Потоки процесса разделяют его программный код, глобальные переменные и системные ресурсы, но каждый поток имеет свой собственный программный счетчик, свое содержимое регистров и свой собственный стек. Теперь процесс представляется как совокупность взаимодействующих потоков и выделенных ему ресурсов. Процесс, содержащий всего один поток исполнения, идентичен процессу в том смысле, который мы употребляли ранее. Для таких процессов мы в дальнейшем будем использовать термин “традиционный процесс”. Иногда потоки называют облегченными процессами или мини-процессами, так как во многих отношениях они подобны традиционным процессам. Потоки, как и процессы, могут порождать потоки-потомки, правда, только внутри своего процесса, и переходить из состояния в состояние. Состояния потоков аналогичны состояниям традиционных процессов. Из состояния рождение процесс приходит содержащим всего один поток исполнения. Другие потоки процесса будут являться потомками этого потока-прародителя. Мы можем считать, что процесс находится в состоянии готовность, если хотя бы один из его потоков находится в состоянии готовность и ни один из потоков не находится в состоянии исполнение. Мы можем считать, что процесс находится в состоянии исполнение, если один из его потоков находится в состоянии исполнение. Процесс будет находиться в состоянии ожидание, если все его потоки находятся в состоянии ожидание. Наконец, процесс находится в состоянии завершил исполнение, если все его потоки находятся в состоянии завершили исполнение. Пока одина поток процесса заблокирован, другой поток того же процесса может выполняться. Потоки разделяют процессор так же, как это делали традиционные процессы, в соответствии с рассмотренными алгоритмами планирования.
Поскольку потоки одного процесса разделяют существенно больше ресурсов, чем различные процессы, то операции создания нового потока и переключения контекста между потоками одного процесса занимают существенно меньше времени, чем аналогичные операции для процессов в целом. Предложенная нами схема совмещения работы в терминах потоков одного процесса получает право на существование.
Поток 1 | Поток 2 | |
Создать поток 2 | ||
Переключение контекста потоков | ||
Ожидание ввода a и b | ||
Переключение контекста потоков | ||
Ввести массив a | ||
Ожидание окончания операции ввода | ||
Ввести массив b | ||
Ожидание окончания операции ввода | ||
Ввести массив с | ||
Ожидание окончания операции ввода | ||
Переключение контекста потоков | ||
a = a + b | ||
Переключение контекста потоков | ||
c = a + c | ||
Вывести массив с | ||
Ожидание окончания операции вывода |
Различают операционные системы, поддерживающие потоки на уровне ядра и на уровне библиотек. Все выше сказанное справедливо для операционных систем, поддерживающих потоки на уровне ядра. В них планирование использования процессора происходит в терминах потоков, а управление памятью и другими системными ресурсами остается в терминах процессов. В операционных системах, поддерживающих потоки на уровне библиотек пользователей, и планирование процессора, и управление системными ресурсами осуществляется в терминах процессов. Распределение использования процессора по потокам в рамках выделенного процессу временного интервала осуществляется средствами библиотеки. В таких системах блокирование одного потока приводит к блокированию всего процесса, ибо ядро операционной системы ничего не знает о существовании потоков. По сути дела, в таких вычислительных системах просто имитируется наличие потоков исполнения.
В дальнейшем тексте этой части книги для простоты изложения мы будем использовать термин “процесс”, хотя все сказанное будет относиться и к потокам исполнения.