Вперед: 1.4.1.2. Многопоточность. Библиотека Pthread
Назад: 1.4. Краткий иллюстративный обзор средств программирования параллельных вычислений
К содержанию: Оглавление


1.4.1. Программирование для систем с общей памятью

1.4.1.1. Ветвление процессов

Современные операционные системы базируются на понятии процесс. Процесс - это некоторая последовательность команд, имеющая собственный контекст и уникальный идентификатор. Под контекстом понимается набор атрибутов процесса - собственный стек исполнения; набор страниц памяти, составляющих адресное пространство процесса; таблицу дескрипторов файлов и т.д. [9].

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

Поскольку основная часть вычислительной работы в программах сосредоточена внутри циклов (тело цикла выполняется много раз и поэтому суммарное время его выполнения составляет основную часть времени выполнения программы), то распараллеливание чаще всего сводится к распараллеливанию выполнения циклов. В нашей задаче мы можем запустить два процесса для выполнения вычислительного цикла, используя для этого системные функции fork(), wait() and exit(). Функция fork() создает новую копию выполняющегося процесса. Новый процесс называется дочерним, а первичный процесс родительским. Значение, возвращаемое функцией fork, позволяет различить родительский и дочерний процессы: родительский процесс получает идентификатор дочернего процесса, а дочерний получает нуль. Используя этот механизм можно поручить различную работу родительскому и дочернему процессам. Дочерний процесс завершается вызовом функции exit(), а родительский ожидает завершения дочернего процесса функцией wait(). Этот механизм ветвления процессов является базовым механизмом в unix-подобных операционных системах. Применительно к параллельным вычисления эта процедура может быть повторена n-1 раз, а планировщик операционной системы автоматически распределит процессы по n процессорам.

Поскольку все процессы независимы и имеют ограниченный набор общих ресурсов, то в программе необходимо предусмотреть операции обмена информацией между процессами. Для этой цели можно использовать функции для работы с общей памятью: shmget (), shmat () и shmctl (). Функция shmget () резервирует сегмент общей памяти, функция shmat() связывает этот сегмент с процессом, а функция shmctl () устанавливает атрибуты для этого сегмента. Когда порождается дочерний процесс, он наследует этот сегмент общей памяти, и таким образом оба процесса получают к нему доступ.

В программе pi_fork.с представлен вариант программы с двумя процессами: родительский процесс выполняет четные шаги цикла i = 2, 4, 6, ..., в то время как дочерний процесс выполняет нечетные шаги i = 1, 3, 5, .... Дочерний процесс хранит свой результат по адресу *shared, а родительский процесс получает значение этой переменной по окончании работы дочернего процесса и прибавляет его к своему значению.

Программа pi_fork.с

#include <stdio.h> 
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
main(int argc, char **argv)
{
 int n, i;
 double d, s, x, pi;
 int shmid, iproc;
 pid_t pid;
 double *shared;
 n = atoi(argv[1]);
 d = 1.0/n;
 shmid = shmget(IPC_PRIVATE, 
         sizeof(double), (IPC_CREAT | 0600));
 shared = shmat(shmid, 0, 0);
 shmctl(shmid, IPC_RMID, 0);
 iproc = 0; 
 if ((pid = fork()) == -1) {
  fprintf(stderr, "The fork failed!\n");
  exit(0);
 } else {
  if (pid != 0) iproc = 1 ;
 }
 s = 0.0;
 for (i=iproc+1; i<=n; i+=2) {
  x = (i-0.5)*d;
  s += 4.0/(1.0+x*x);
 }
 pi = d*s;
 if (pid == 0) {
  *shared = pi;
  exit(0); 
 } else { 
  wait(0); 
  pi = pi + *shared;
  printf("pi=%.15f\n", pi);
 }
}

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



Вперед: 1.4.1.2. Многопоточность. Библиотека Pthread
Назад: 1.4. Краткий иллюстративный обзор средств программирования параллельных вычислений
К содержанию: Оглавление