Вперед: 1.4.1.3. Многопоточное программирование на языке Java
Назад: 1.4.1.1. Ветвление процессов
К содержанию: Оглавление


1.4.1.2. Многопоточность. Библиотека Pthread

Системная функция fork() порождает полную копию родительского процесса, и эта процедура является затратной как с точки зрения расходования процессорного времени, так и с точки зрения расходования оперативной памяти. В параллельном программировании зачастую достаточно использования частичной копии процесса. Такая частичная копия называется потоком, <легковесным процессом> или нитью (thread). Нить - это поток команд, который может самостоятельно управляться планировщиком. В отличие от дочернего процесса нить создается в контексте головного процесса, в том же самом адресном пространстве. На однопроцессорных системах планировщик чередует выполнение нитей посредством квантования процессорного времени, а на многопроцессорных системах нити могут выполняться параллельно на различных процессорах. Потоки не требуют специального механизма взаимодействия между подзадачами, поскольку они имеют равноправный доступ к общей памяти родительского процесса.

Для работы с нитями в разных версиях ОС UNIX существовали свои библиотеки. В настоящее время в качестве стандарта для работы с нитями принята библиотека Pthread [10]. Пользовательский интерфейс библиотеки определен в стандарте POSIX 1003.1 1995, разработанном комитетами по выпуску стандартов ANSI/IEEE. Библиотека Pthread содержит более 60 функций, которые можно разделить на три категории: функции управления потоками, функции управления мьютексами (mutex - взаимное исключение) и функции управления переменными окружения.

В программе pi_thread.с представлена версия вычисления числа π с использованием библиотеки Pthread. Программа создает нить, используя функцию pthread_create (), затем назначает уникальный идентификатор переменной типа pthread_t. Головная программа вызывает вычислительную функцию, которая будет выполняться как нить. Функция pthread_exit () используется для завершения <легковесного> процесса. Функция pthread_join () является аналогом функции wait(), но в этом случае любая нить может связываться с любой другой нитью в процессе, так как для нитей нет жесткой связи родитель-потомок.

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

Головная программа запускается из командной строки с двумя аргументами: n - числом точек разбиения интервала интегрирования и num_threads - числом порождаемых нитей. Значение этой глобальной переменной может быть присвоено и за пределами программы в виде переменной окружения оболочки Shell. Первая нить вычислительной функции PIworker, получает свой идентификатор i от головного процесса и выполняет n/num_threads часть вычислительного цикла. Поскольку доступ к глобальной переменной pi не должны получить больше чем одна нить одновременно, то операция ее модификации запирается механизмом взаимной блокировки (mutex).Рассмотренный алгоритм реализует следующая программа.

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

#include <stdio.h> 
#include <pthread.h> 
int n, num_threads;
double d, pi;
pthread_mutex_t reduction_mutex;
pthread_t *tid;

void *PIworker(void *arg)
{
 int i, myid;
 double s, x, mypi;
 myid = *(int *)arg;
 s = 0.0;
 for (i=myid+1; i<=n; i+=num_threads) {
  x = (i-0.5)*d;
  s += 4.0/(1.0+x*x);
 }
 mypi = d*s;
 pthread_mutex_lock(&reduction_mutex);
 pi += mypi;
 pthread_mutex_unlock(&reduction_mutex);
 pthread_exit(0);
}

main(int argc, char **argv)
{
 int i;
 int *id;
 n = atoi(argv[1]);
 num_threads = atoi(argv[2]);
 d = 1.0/n;
 pi = 0.0;
 id = (int *) calloc(n,sizeof(int));
 tid = (pthread_t *) calloc(num_threads,
               sizeof(pthread_t));
 if(pthread_mutex_init(&reduction_mutex,NULL)) {
  fprintf(stderr, "Cannot init lock\n");
  exit(0); 
 };
 for (i=0; i<num_threads; i++) {
  id[i] = i;
  if(pthread_create(&tid[i],NULL,
             PIworker,(void *)&id[i])) {
   exit(1); 
  }; 
 };
 for (i=0; i<num_threads; i++)
  pthread_join(tid[i],NULL);
 printf("pi=%.15f\n", pi);
}

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



Вперед: 1.4.1.3. Многопоточное программирование на языке Java
Назад: 1.4.1.1. Ветвление процессов
К содержанию: Оглавление