Параллельное и распределенное программирование на С++ - Хьюз Камерон
posix_spawnattr_init(&Attr) ;
posix_spawn_file_actions_init (&FileActions) ;
posix_spawn(&Pid, «TaskType4», &FileActions,&Attr,argv4, NULL);
posix_spawn(&Pid, «TaskType5», &FileActions,&Attr,argv5, NULL);
if(Y){
posix_spawn(&Pid,«TaskType6»,&FileActions,&Attr, argv6,NULL);
}
// Подобно хорошему родителю, ожидаем возвращения // своих «детей».
wait(&stat);
wait(&stat);
wait(&stat);
return(0);
}
// Все TaskType-задачи должны быть аналогичными. //.. .
int main(int argc, char *argv[]){
int Rt; //. . .
if(argv[1] == X){
// Инициализируем структуры.
posix_spawn(&Pid,«TaskTypeX»,&FileActions,&Attr,..., NULL);
else{
// Инициализируем структуры.
//.. •
posix_spawn(&Pid,«TaskTypeY», &FileActions,&Attr, ...,NULL);
}
wait(&stat); exit(0);
}
В листинге 3.6 тип каждой задачи (а следовательно, и тип порождаемого процесса) определяется на основе информации, передаваемой от родительского процесса или сценария оболочки.
Линии видимого контура
Порождение процессов, как показано в листинге 3.7, возможно с помощью функций, вызываемых из функции main ().
// Листинг 3.7. Стержневая ветвь программы, из которой // вызывается функция, порождающая процесс
int main(int argc, char *argv[]) {
Rt = funcl(X, Y, Z); //.. .
}
// Определение функции.
int funcl(char *M, char *N, char *V) {
//.. .
char *const args[] = {«TaskX»',M,N,V,NULL};
Pid = fork();
if(Pid == 0) {
exec(«TaskX»,args);
}
if(Pid > 0) {
//.. .
}
wait(&stat);
}
В листинге 3.7 функция funcl () вызывается с тремя аргументами. Эти аргументы передаются порожденному процессу.
Процессы также могут порождаться из методов, принадлежащих объектам. Как показано в листинге 3.8, объекты можно объявить в любом процессе.
// Лист инг 3.8. Объявление объекта в процессе //-••
my_pbject MyObject; //-••
// Объявление и определение класса.
class my_object {
public: //...
int spawnProcess(int X); //...
};
int my_object::spawnProcess(int X) {
//.. .
// posix__spawn() или system() //.. .
}
Как показано в листинге 3.8, объект может создавать любое количество процессов из любого метода.
Резюме
Параллелизм в С++-программе достигается за счет ее разложения на несколько процессов или несколько потоков. Процесс- это «единица работы», создаваемая операционной системой. Если программа- это артефакт (продукт деятельности) разработчика, то процесс - это артефакт операционной системы. Приложение может состоять из нескольких процессов, которые могут быть не связаны с какой-то конкретной программой. Операционные системы способны управлять сотнями и даже тысячами параллельно загруженных процессов.
Некоторые данные и атрибуты процесса хранятся в блоке управления процессами (process control block - PCB), или БУП, используемом операционной системой для идентификации процесса. С помощью этой информации операционная система Управляет процессами. Многозадачность (выполнение одновременно нескольких процессов) реализуется путем переключения контекста. Текущее состояние выполняемого процесса и его контекст сохраняются в БУП-блоке, что позволяет успешно возобновить этот процесс в следующий раз, когда он будет назначен центральному процессору. Занимая процессор, процесс пребывает в состоянии выполнения, а когда он ожидает использования ЦП, - то в состоянии готовности (ожидания). Получить информацию о процессах, выполняющихся в системе, можно с помощью утилиты ps.
Процессы, которые создают другие процессы, вступают с ними в «родственные» (отцы- и -дети) отношения. Создатель процесса называется родительским, а созданный процесс — сыновним. Сыновние процессы наследуют от родительских множество атрибутов. «Святая обязанность» родительского процесса — подождать, пока сыновний не покинет систему. Для создания процессов предусмотрены различные системные функции: fork (), fork-exec (), system() и posix_spawn (). Функции fork(), fork-exec() и posix_spawn() создают процессы, которые являются асинхронными, в то время как функция system() создает сыновний процесс, который является синхронным по отношению к родительскому. Асинхронные родительские процессы могут вызвать функцию wait (), после чего «синхронно» ожидать, пока сыновние процессы не завершатся или пока не будут считаны коды завершения для уже завершившихся сыновних процессов.
Программу можно разбить на несколько процессов. Эти процессы может породить родительский процесс, либо они могут быть запущены из сценария оболочки как отдельные выполняемые программы. Специализированные процессы могут при необходимости порождать другие процессы, предназначенные для выполнения действий только определенного типа. Порождение процессов может быть осуществлено как из функций, так и из методов.
Разбиение C++ программ на множество потоков
Непрерывное усложнение компьютерных систем вселяет в нас надежду, что мы и в дальнейшем сможем успешно управлять этим видом абстракции. — Эндрю Кёниг и Барбара My(Andrew Koening and Barbara Moo), Ruminations on С++
Работу любой последовательной программы можно разделить между несколькими подпрограммами. Каждой подпрограмме назначается конкретная задача, и все эти задачи выполняются одна за другой. Вторая задача не может начаться до тех пор, пока не завершится первая, а третья — пока не закончится вторая и т.д. Описанная схема прекрасно работает до тех пор, пока не будут достигнуты границы производительности и сложности. В одних случаях единственное решение проблемы производительности — найти возможность выполнять одновременно более одной задачи. В других ситуациях работа подпрограмм в программе настолько сложна, что имеет смысл представить эти подпрограммы в виде мини-программ, которые выполняются параллельно внутри основной программы. В главе 3 были представлены методы разбиения одной программы на несколько процессов, каждый из которых выполняет отдельную задачу. Такие методы позволяют приложению в каждый момент времени выполнять сразу несколько действий. Однако в этом случае каждый процесс имеет собственные адресное пространство и ресурсы. Поскольку каждый процесс занимает отдельное адресное пространство, то взаимодействие между процессами превращается в настоящую проблему. Для обеспечения связи между раздельно выполняемыми частями общей программы нужно реализовать такие средства межпроцессного взаимодействия, как каналы, FIFO-очереди (с дисциплиной обслуживания по принципу «первым пришел — первым обслужен») и переменные среды. Иногда нужно иметь одну программу (которая выполняет несколько задач одновременно), не разбивая ее на множество мини-программ. В таких обстоятельствах можно использовать потоки. Потоки позволяют одной программе состоять из параллельно выполняемых частей, причем все части имеют доступ к одним и тем же переменным, константам и адресному пространству в целом. Потоки можно рассматривать как мини-программы в основной программе. Если программа разделена на несколько процессов, как было показано в главе 3 , то с выполнением каждого отдельного процесса связаны определенные затраты системных ресурсов. Для потоков требуется меньший объем затрат системных ресурсов. Поэтому потоки можно рассматривать как облегченные процессы, т.е. они позволяют воспользоваться многими преимуществами процессов без больших затрат на организацию взаимодействия между ними. Потоки обеспечивают средства разделения основного «русла» программы на несколько параллельно выполняемых «ручейков».