Спонсор: Microsoft
Опубликован: 13.11.2010 | Уровень: для всех | Доступ: платный | ВУЗ: Санкт-Петербургский государственный университет
Лекция 9:

Методы взаимодействия процессов

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >
Аннотация: В лекции рассматриваются: взаимодействие процессов: проблема ограниченного буфера; проблема "производитель – потребитель"; прямая и косвенная связь процессов; клиент-серверная взаимосвязь; сокетная связь; удаленный вызов процедуры (RPC) и удаленный вызов метода (RMI); выстраивание параметров (marshaling).

Презентацию к данной лекции Вы можете скачать здесь.

Введение

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

Независимые и взаимодействующие процессы

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

Независимый процесс – процесс, никак не связанный с другими процессами, который не может влиять на исполнение других процессов или испытывать их влияние.

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

Преимущества взаимодействующих процессов очевидны:

  • Совместное использование данных ; процессы могут работать с общими данными, при условии их синхронизации (рассматриваемой в следующих лекциях);
  • Ускорение вычислений ;
  • Модульность: организация взаимодействующих процессов – это метод параллельного решения задачи, декомпозируемой на относительно независимые части, части, каждую из которых решает один из взаимодействующих процессов
  • Удобство.

Виды организации взаимосвязи процессов

С точки зрения видов взаимосвязи родительского и дочернего процессов, процессы подразделяются на независимые, подчиненные и сопроцессы.

Подчиненный процесс – процесс, зависящий от процесса-родителя. Подчиненный процесс уничтожается при уничтожении родительского процесса, как в системах UNIX и ОС "Эльбрус". Процесс-родитель перед своим завершением должен ожидать завершения всех своих подчиненных процессов.

Независимый процесс – дочерний процесс, выполняемый независимо от процесса-родителя. Типичные примеры: процессы-демоны в UNIX, запускаемые начальным процессом init. Например, cron – процесс-демон, организующий вызов заданных в специальной таблице crontab действий с заданной периодичностью (автоматическое резервное копирование всех файловых систем на ленту в полночь); smbd – процесс-демон, управляющий серверным программным обеспечением SAMBA для сетевого доступа с Windows-машин к файлам UNIX-машины.

Сопроцесс (coprocess, coroutine) – процесс, равноправно взаимодействующий с другими такими же процессами; хранит свое текущее локальное управление (program counter); взаимодействует с другим сопроцессом Q с помощью операций resume (Q).Взаимодействие нескольких сопроцессов друг с другом операторами resume полностью равноправно. Данный механизм взаимодействия принципиально отличается от вызова процедуры. Операция detach ( открепить ) переводит сопроцесс в пассивное состояние, в котором могут быть доступны только его глобальные данные, но его программа уже завершена и не подлежит повторному запуску. Сопрограммное взаимодействие реализовано в языке СИМУЛА-67, который, как известно, стал родоначальником и объектно-ориентированного подхода.

Классификация процессов, близкая к приведенной в данном разделе, реализована в ОС "Эльбрус".

Парадигма (шаблон) взаимодействия процессов: производитель – потребитель

Реализация взаимодействия процессов может быть основана на одной из классических парадигм (шаблонов), сложившейся за десятилетия развития программирования. В данном разделе рассмотрим одну из наиболее распространенных из парадигм взаимодействия процессов - производитель потребитель: процесс-производитель ( producer ) генерирует в некотором буфере информацию, которая используется процессом-потребителем ( consumer ).

При реализации данной парадигмы возможны схемы с неограниченным и ограниченным буфером, используемым для связи двух процессов.

  • Схема с неограниченным буфером (unbounded buffer) подразумевает, что на размер используемого буфера теоретически нет ограничений.
  • Схема с ограниченным буфером (bounded buffer) предполагается определенное ограничение размера буфера, например, константой BUFFER_SIZE.

При реализации следует учесть, что схема с ограниченным буфером, с точки зрения принципов надежных и безопасных вычислений (trustworthy computing, см. "Понятие операционной системы (ОС), цели ее работы. Классификация компьютерных систем" ), представляет опасность атаки "переполнение буфера"(buffer overrun) – ошибочного или преднамеренного превышения размера буфера. Чтобы избежать этой уязвимости, при заполнении буфера необходимо проверять его размер.

Реализуем ограниченный буфер следующим образом. Информация хранится в массиве с двумя указателями: in - для считывания и использования очередного элемента информации процессом-потребителем и out - для записи очередного сгенерированного элемента информации процессом-производителем. При считывании из буфера очередной элемент удаляется, и указатель in, соответственно, продвигается. При записи в буфер продвигается указатель out. Для удобства будем считать буфер циклическим, т.е. при его заполнении следующим заполняемым элементом будет нулевой (если он освободился), следующим после него – первый и т.д. Таким образом, процесс-производитель должен вычислять индекс в буфере, по которому он записывает следующий элемент, по формуле ( out + 1) % BUFFER_SIZE, где " % " операция взятия остатка от деления. Аналогично, процесс-потребитель должен вычислять индекс следующего элемента информации в буфере по формуле ( in + 1) % BUFFER_SIZE. Учтем также две возможных ситуации: переполнение буфера (при генерации производителем числа элементов, большего длины буфера) и исчерпание буфера (в случае, если потребитель взял из буфера последний на данный момент сгенерированный элемент). Чтобы избежать обращения за границы буфера, при переполнении буфера производитель должен будет ждать, пока в буфере не освободится хотя бы один элемент, а при исчерпании буфера должен будет ждать потребитель, пока хотя бы один новый элемент не появится в буфере.

Реализация представления буфера на языке Си может иметь вид:

#define BUFFER_SIZE 1000 /* или другое конкретное значение */
typedef struct {
	. . .
} item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

Реализация схемы алгоритма процесса-производителя имеет вид:

item nextProduced; /* следующий генерируемый элемент */
	while (1) { /* бесконечный цикл */
	while (((in + 1) % BUFFER_SIZE) == out)
		; /* ждать, пока буфер переполнен */
	buffer[in] = nextProduced; /* генерация элемента */
	in = (in + 1) % BUFFER_SIZE;
}

Соответственно, реализация процесса-потребителя будет иметь вид:

item nextConsumed; /* следующий используемый элемент */
while (1) { /* бесконечный цикл */
	while (in == out)
		; /* ждать, пока буфер пуст */
	nextConsumed = buffer[out]; /* использование элемента */
	out = (out + 1) % BUFFER_SIZE;
}

Данный код может быть использован как шаблон (pattern) для реализации схемы производитель – потребитель в любой системе.

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >
Василий Четвертаков
Василий Четвертаков
Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!

Константин Леденев
Константин Леденев
Россия
Олег Равков
Олег Равков
Россия