Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1179 / 142 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 2:

Средства синхронизации потоков управления

В стандарте POSIX-2001 определены четыре типа мьютексов.

PTHREAD_MUTEX_NORMAL

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

PTHREAD_MUTEX_ERRORCHECK

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

PTHREAD_MUTEX_RECURSIVE

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

PTHREAD_MUTEX_DEFAULT

Подразумеваемое значение атрибута " тип ". Некорректные действия приводят к неопределенному эффекту. Реализация может отождествить этот тип с одним из вышеописанных.

Атрибут " верхняя грань приоритетов выполнения " определяет минимальный приоритет, с которым будет выполняться критический интервал, охраняемый мьютексом. Чтобы избежать инверсии приоритетов, значение этого атрибута следует сделать не меньшим, чем максимальный из приоритетов потоков, могущих захватить данный мьютекс (отсюда и название атрибута ). Диапазон допустимых значений атрибута тот же, что и для приоритетов политики планирования SCHED_FIFO .

Атрибут " протокол " влияет на планирование потока управления во время владения мьютексом. Согласно стандарту, возможных протоколов три.

PTHREAD_PRIO_NONE

Владение мьютексом не влияет на приоритет и планирование потока управления.

PTHREAD_PRIO_INHERIT

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

PTHREAD_PRIO_PROTECT

Если поток управления владеет мьютексами данного типа, он выполняется с приоритетом, являющимся максимумом из верхних граней приоритетов выполнения этих мьютексов, независимо от того, ждут ли какие-либо другие потоки их освобождения.

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

Если поток управления владеет несколькими мьютексами с разными протоколами, ему назначается приоритет, максимальный из предписанных каждым из протоколов.

Признак использования несколькими процессами мьютекса, в соответствии со стандартом POSIX-2001, может принимать два значения.

PTHREAD_PROCESS_PRIVATE

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

PTHREAD_PROCESS_SHARED

С мьютексом могут работать все потоки управления, имеющие доступ к памяти, в которой мьютекс расположен, даже если эта память разделяется несколькими процессами.

На попытки захвата мьютекса, осуществляемые с применением функций pthread_mutex_lock(), pthread_mutex_trylock() или pthread_mutex_timedlock() значения атрибутов влияют так, как описано выше.

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

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

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

Функция pthread_mutex_unlock() обычно освобождает мьютекс. Для мьютексов типа PTHREAD_MUTEX_RECURSIVE вызов pthread_mutex_unlock(), строго говоря, приводит лишь к уменьшению счетчика на единицу, а освобождение происходит только тогда, когда счетчик становится нулевым. Кому достанется мьютекс после освобождения, зависит от политики планирования.

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

#ifndef g_MALLOC
#define g_MALLOC

/* Количество размеров (в словах типа size_t), */
/* для которых поддерживаются */
/* разные списки памяти */
#define DIF_SIZES         8

/* Размер пула памяти */
#define POOL_SIZE         65536

/* Указатель на кусок памяти нулевого размера */
#define g_NULL  ((void *) (-1))

/* Первое поле следующей структуры нужно
/* для всех кусков памяти, а второе – */
/* только для провязки свободных.*/
/* При отведении памяти адрес второго */
*/ поля выдается как результат */
typedef struct listi {
    size_t length;
    struct listi *pnext;
} *list_of_mem;

extern void *g_malloc (size_t);
extern void g_free (void *);

#endif
Листинг 2.7. Заголовочный файл g_malloc.h для функций выделения и освобождения памяти в многопотоковой среде.
/* * * * * * * * * * * * * * * * * * * * * */
/* Функции выделения и освобождения памяти */
/* * * * * * * * * * * * * * * * * * * * * */

#include <pthread.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include "g_malloc.h"

static char mem_pool [POOL_SIZE] = {0, };
/* Размер занятой части  пула памяти */
/* Списки свободного пространства */
/* (по одному на каждый размер */
/* от 1 до DIF_SIZES) */
static size_t cur_pool_size = 0;

static list_of_mem short_lists [DIF_SIZES] =
    {NULL, NULL, NULL, NULL, NULL,
     NULL, NULL, NULL};
/* Список больших */
/* свободных кусков (превосходящих DIF_SIZES) */
/* Разные мьютексы для разных */
/* групп списков свободного пространства */
static list_of_mem big_list = NULL;

static pthread_mutex_t short_lists_mutex = 
        PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t big_list_mutex = 
        PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t pool_mutex = 
        PTHREAD_MUTEX_INITIALIZER;

/* * * * * * * * * * */
/* Выделение памяти     */
/* * * * * * * * * * */
void *g_malloc (size_t size) {
/* Указатель для хождения по списку */
/* больших свободных кусков */
    list_of_mem *p; 
/* Указатель для хождения по спискам */
/* свободных кусков */
    list_of_mem pt;
    /* Индекс в массиве short_lists */
    size_t ls; 
    size_t ts; /* Временная переменная */

    if (size == 0) {
        return (g_NULL);
/* Важно, чтобы результат был */
/* отличен от NULL, поскольку NULL */
/* – признак ненормального завершения */
    }

    /* Округлим запрошенный размер вверх */
    /* до кратного размеру size_t и */
    /* прибавим слово служебной информации */
    size = (size – 1 + 2 * sizeof (size_t)) 
            & ~(sizeof (size_t) – 1);

    /* Вычислим индекс в массиве */
    /*  short_lists [], соответствующий */
    /* запрошенному размеру */
    ls = size / sizeof (size_t) – 2;

    if (ls < DIF_SIZES) {
        /* Попробуем выдать кусок */
        /* из списка коротких */
        assert (
            pthread_mutex_lock(
              &short_lists_mutex) == 0);
        if ((pt = short_lists [ls]) !=
                  NULL) {
            /* Есть нужный кусок */
          short_lists [ls] =
           (short_lists [ls])->pnext;
          assert (pthread_mutex_unlock(
            &short_lists_mutex) == 0);
          return (&pt->pnext);
        }
        assert (pthread_mutex_unlock(
          &short_lists_mutex) == 0);
    }

    /* Попробуем выдать кусок из */
    /* списка больших */
    assert (pthread_mutex_lock(
      &big_list_mutex) == 0);
    for (p = &big_list, pt = *p; pt != NULL; 
          p = &pt->pnext, pt = *p) {
        if ((signed long) (ts =
          pt->length – size) >= 0) {
          /* Нашли подходящий кусок */
            if (ts < sizeof (*pt)) {
    /* Придется выдать кусок целиком – */
    /* в остатке не помещается */
    /* служебная информация */
    *p = pt->pnext;
        } else {
        /* Отрежем сколько надо и, */
        /* при необходимости,     */
        /* перецепим остаток в */
        /* список коротких     */
        if ((ls = (pt->length = ts) /
             sizeof (size_t) – 2) 
                < DIF_SIZES) {
        *p = pt->pnext;
        assert (pthread_mutex_lock(
          &short_lists_mutex) == 0);
        pt->pnext = short_lists [ls];
        short_lists [ls] = pt;
        assert (pthread_mutex_unlock(
          &short_lists_mutex) == 0);
            }
        pt = (list_of_mem) ((char *) pt
          + ts);
        pt->length = size;
            }
            assert (
             pthread_mutex_unlock(
              &big_list_mutex) == 0);
            return (&pt->pnext);
        }
    } /* for */
    assert (pthread_mutex_unlock (
      &big_list_mutex) == 0);

    /* Кусок из большого списка */
    /* выдать не удалось. */
    /* Попробуем взять прямо из */
    /* пула памяти */
    assert (pthread_mutex_lock(
      &pool_mutex)
      == 0);
    if (cur_pool_size + size <= POOL_SIZE) {
        pt = (list_of_mem) (mem_pool
          + cur_pool_size);
        pt->length = size;
        cur_pool_size += size;
        assert (pthread_mutex_unlock (
          &pool_mutex) == 0);
        return (&pt->pnext);
    }
    assert (pthread_mutex_unlock(
      &pool_mutex) == 0);

    /* Неудача при выделении памяти */
    errno = ENOMEM;
    return (NULL);
}

/* * * * * * * * * * * * * * * * * * */
/* Возврат ранее запрошенной памяти */
/* * * * * * * * * * * * * * * * * * */
void g_free (void *p) {
    list_of_mem pt;
    size_t size, ls;

    if ((p == g_NULL) || (p == NULL)) {
        return;
    }

    /* Установим указатель на */
    /* служебную информацию */
    pt = (list_of_mem) ((char *) p
      – sizeof (size_t));
    size = pt->length;
    ls = size / sizeof (size_t) – 2;
    memset (p, 0, size – sizeof (size_t));

    /* Не из конца ли пула этот кусок? */
    assert (pthread_mutex_lock(
      &pool_mutex) == 0);
    if (((char *) pt + size) ==
      (mem_pool + cur_pool_size)) {
        pt->length = 0;
        cur_pool_size -= size;
        assert (pthread_mutex_unlock(
          &pool_mutex) == 0);
        return;
    }
    assert (pthread_mutex_unlock(
      &pool_mutex) == 0);

/* Добавим освободившийся кусок */
/* к одному из списков */
    if (ls < DIF_SIZES) {
        assert (pthread_mutex_lock(
          &short_lists_mutex) == 0);
        pt->pnext = short_lists [ls];
        short_lists [ls] = pt;
        assert (pthread_mutex_unlock(
          &short_lists_mutex) == 0);
    } else {
        /* Добавим к большому списку */
        assert (pthread_mutex_lock(
          &big_list_mutex) == 0);
        pt->pnext = big_list;
        big_list = pt;
        assert (pthread_mutex_unlock(
          &big_list_mutex) == 0);
    }
}
Листинг 2.8. Исходный текст функций выделения и освобождения памяти в многопотоковой среде.

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

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

Для библиотечных функций удобны предоставляемые стандартом POSIX-2001 средства инициализации статически описанных мьютексов.

На листинге 2.9 показана тестовая многопотоковая программа, а на листинге 2.10 – возможные результаты ее работы. Видно, что выполнение потоков управления чередуется, и потоки влияют друг на друга.

#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include "g_malloc.h"

static void *start_func (void *dummy) {
    void *p1, *p2, *p3;

    printf ("g_malloc (65000): %p\n",
      p1 = g_malloc (65000));
    sleep (1);
    printf ("g_malloc (1): %p\n",
      p2 = g_malloc (1));
    sleep (1);
    g_free (p1);
    sleep (1);
    g_free (p2);
    sleep (1);
    printf ("g_malloc (64990): %p\n",
      p1 = g_malloc (64990));
    sleep (1);
    printf ("g_malloc (1): %p\n",
      p2 = g_malloc (1));
    sleep (1);
    printf ("g_malloc (5): %p\n",
      p3 = g_malloc (5));
    sleep (1);
    g_free (p1);
    sleep (1);
    g_free (p2);
    sleep (1);
    g_free (p3);
    sleep (1);
    printf ("g_malloc (100000): %p\n",
      p3 = g_malloc (100000));

    return (NULL);
}

int main (void) {
    pthread_t pt1, pt2;

    pthread_create (&pt1, NULL,
      start_func, NULL);
    pthread_create (&pt2, NULL,
      start_func, NULL);

    pthread_join (pt1, NULL);
    pthread_join (pt2, NULL);

    return (0);
}
Листинг 2.9. Пример программы, использующей функции выделения и освобождения памяти в многопотоковой среде.
g_malloc (65000): 0x8049024
g_malloc (65000): (nil)
g_malloc (1): 0x8058e10
g_malloc (1): 0x8058e18
g_malloc (64990): 0x804902c
g_malloc (64990): (nil)
g_malloc (1): 0x8049024
g_malloc (1): 0x8058e10
g_malloc (5): 0x8058e18
g_malloc (5): 0x8058e24
g_malloc (100000): (nil)
g_malloc (100000): (nil)
Листинг 2.10. Возможные результаты работы программы, использующей функции выделения и освобождения памяти в многопотоковой среде.

Мьютексы – весьма подходящее средство для реализации обеда философов. Ниже приведена программа, написанная С.В. Самборским (см. листинг 2.11).

/*
Обедающие философы. Многопотоковая
реализация с помощью мьютексов. Запуск:
    mudrecMutex [-a | -p | -I]
      [-t число_секунд] имя_философа ...

Опции:
        -t число_секунд – сколько секунд
         моделируется

        Стратегии захвата вилок:
        -a – сначала захватывается вилка
             с меньшим номером;
        -p – сначала захватывается
             нечетная вилка;
        -I – некорректная (но
             эффективная) интеллигентная 
             стратегия: во время
             ожидания уже захваченная
             вилка кладется.

Пример запуска:
          mudrecMutex -p -t 300 A B C D E F G\
                                H I J K L M N\
                                O P Q R S T U\
                                V W X Y Z
*/

static char rcsid[] __attribute__((unused)) = \
 "$Id: mudrecMutex.c,v 1.14 2003/11/20 16:09:20"
 "sambor Exp $";

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>

#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)>(b)?(b):(a))

struct mudrec {
    char *name;
    int left_fork, right_fork;
    int eat_time, wait_time, think_time,
       max_wait_time;
    int count;
    pthread_t thread;
    int private_pFdIn;
} *kafedra;

/* Глобальные счетчики и логические переменные */
int Stop = 0; /* Признак конца обеда */

/* Различные дескрипторы */
int protokol [2] = {-1, -1};
#define pFdIn (protokol [1])
#define pFdOut (protokol [0])

/* Массив мьютексов для синхронизации доступа */
/* к вилкам */
pthread_mutex_t *mtxFork;

/* Разные алгоритмы захвата вилок */
static void get_forks_simple (
  struct mudrec *this);
static void get_forks_odd (
  struct mudrec *this);
static void get_forks_maybe_infinit_time(
  struct mudrec *this);

/* Используемый метод захвата вилок */
void (*get_forks) (struct mudrec *this) =
  get_forks_simple;

/* Возвращение вилок */
static void put_forks (struct mudrec *this);

/*Потоки-философы */
void *filosof (void *arg) {
    struct mudrec *this = arg;
    char buffer [LINE_MAX];
    int bytes;
    int private_pFdIn = this->private_pFdIn;

    while (!Stop) {
        /* Пора подкрепиться */
        {
            int wait_time, tm =
              time (NULL);

            bytes = write (
              private_pFdIn, buffer, 
              strlen (buffer));

            (*get_forks) (this);

            wait_time =
              time (NULL) – tm;
            this->wait_time +=
              wait_time;
            this->max_wait_time =
              max (wait_time, 
              this->max_wait_time);

        sprintf(
         buffer,
         "%s: ждал вилок %d сек\n", 
         this->name, wait_time);
         bytes = write (private_pFdIn,
                        buffer, 
                        strlen (buffer));
        }

        /* Может, обед уже закончился? */
        if (Stop) {
            put_forks (this);
            break;
        }

        /* Ест */
        {
            int eat_time =
              rand () % 20 + 1;

            sleep (eat_time);

            this->eat_time +=
              eat_time;
            this->count++;
            sprintf (buffer,
              "%s: ел %d сек\n",
              this->name, 
              eat_time);
            bytes = write (
               private_pFdIn, buffer, 
               strlen (buffer));
        }

        /* Отдает вилки */
        put_forks (this);

        if (Stop) break;

        /* Размышляет */
        {
            int think_time =
              rand () % 10 + 1;

            sleep (think_time);
            this->think_time +=
              think_time;
        }
    } /* while (!Stop) */

    sprintf (buffer,"%s: уходит\n",
             this->name);
    bytes = write (private_pFdIn,
                   buffer, strlen (buffer));

    close (private_pFdIn);

    return (NULL);
} /* Поток-философ */

/* Кладет вилки одну за другой */
static void put_forks (struct mudrec *this) {
    pthread_mutex_unlock (
      &mtxFork [this->left_fork – 1]);
    pthread_mutex_unlock (
      &mtxFork [this->right_fork – 1]);
}

/* Берет вилки по очереди в порядке номеров */
static void get_forks_simple(
     struct mudrec *this) {
    int first = min (
       this->left_fork, this->right_fork);
    int last = max (
       this->left_fork, this->right_fork);
    pthread_mutex_lock (
       &mtxFork [first – 1]);
    pthread_mutex_lock (
       &mtxFork [last – 1]);
}

/* Берем сначала нечетную вилку */
/* (если обе нечетные – */
/* то с большим номером) */
static void get_forks_odd (struct mudrec *this)
{
    int left = this->left_fork, right =
      this->right_fork;

    int first;
    int last;

    if ((left & 1) > (right & 1)) {
        first = left;
        last  = right;
    } else if ((left & 1) < (right & 1)) {
        first = right;
        last  = left;
    } else {
        first = max (left, right);
        last  = min (left, right);
    }

    pthread_mutex_lock (
      &mtxFork [first – 1]);
    pthread_mutex_lock (
      &mtxFork [last – 1]);
}

/* Берем вилки по очереди, в */
/* произвольном порядке.    */
/* Но если вторая вилка не */
/* берется сразу, то кладем */
/* первую. */
/* То есть философ не расходует */
/* вилочное время впустую.    */
static void get_forks_maybe_infinit_time 
        (struct mudrec *this) {
    int left = this->left_fork, right =
      this->right_fork;

    for (;;) {
        pthread_mutex_lock (
          &mtxFork [left – 1]);
        if (0 == pthread_mutex_trylock 
            (&mtxFork [right – 1]))
             return;
        pthread_mutex_unlock (
          &mtxFork [left – 1]);
        pthread_mutex_lock (
          &mtxFork [right – 1]);
        if (0 == pthread_mutex_trylock 
            (&mtxFork [left – 1]))
        return;
        pthread_mutex_unlock (
          &mtxFork [right – 1]);
    }
}

/* Мелкие служебные функции */
static void stop (int dummy) {
    Stop = 1;
}

static void usage (char name []) {
    fprintf (stderr,
        "Использование: %s"
        " [-a | -p | -I] "
        "[-t число_секунд] "
        "имя_философа ...\n", name);
    exit (1);
}

/* Точка входа демонстрационной программы */
int main (int argc, char *argv []) {
    char buffer [LINE_MAX], *p;
    int i, n, c;
    int open_room_time = 300;
    int nMudr;
    struct sigaction sact;

    while ((c = getopt (argc, argv,
      "apIt:")) != -1) {
        switch (c) {
        case 'a':
          get_forks =
            get_forks_simple;
           break;
        case 'p':
          get_forks =
            get_forks_odd;
           break;
        case 'I':
          get_forks = 
            get_forks_maybe_infinit_time;
          break;
        case 't':
          open_room_time = strtol(
            optarg, &p, 0);
        if (optarg [0] == 0 || *p != 0)
          usage (argv [0]);
        break;
        default : usage (argv [0]);
        }
    }

    nMudr = argc – optind;
    /* Меньше двух */
    /* философов неинтересно ... */
    if (nMudr < 2) usage (argv [0]); 

    /* Создание канала для протокола */
    /* обработки событий */
    pipe (protokol);
kafedra = calloc (sizeof (struct mudrec), nMudr);

    /* Зачисление на кафедру */
    for (i = 0; i < nMudr; i++, optind++) {
        kafedra [i].name = argv [optind];
        /* Выдадим телефон */
        kafedra [i].private_pFdIn =
          fcntl (pFdIn, 
          F_DUPFD, 0);

        /* Укажем новичку, */
        /* какими вилками */
        /* пользоваться */
        kafedra [i].left_fork = i + 1;
        kafedra [i].right_fork = i + 2;
    }
    /* Последний */
    /* пользуется вилкой первого */
    kafedra [nMudr – 1].right_fork = 1; 

    /* Зададим реакцию на сигналы */
    /* и установим будильник */
    /* на конец обеда */
    sact.sa_handler = stop;
    (void) sigemptyset (&sact.sa_mask);
    sact.sa_flags = 0;
    (void) sigaction (SIGINT, &sact, 
        (struct sigaction *) NULL);
    (void) sigaction (SIGALRM, &sact, 
        (struct sigaction *) NULL);
    alarm (open_room_time);

    /* Создадим мьютексы для охраны вилок */
    mtxFork = calloc (
      sizeof (pthread_mutex_t),
      nMudr);
    for (i = 0; i < nMudr; i++) {
        pthread_mutex_init (
          &mtxFork [i],
          NULL);
    }

    /* Философы входят в столовую */
    for (i = 0; i < nMudr; i++)
        pthread_create (
           &kafedra [i].thread,
           NULL, &filosof, 
        (void *) &kafedra [i]);

/* Выдача сообщений на стандартный */
/* вывод и выход */
/* после окончания всех задач */
    close (pFdIn);
    while (1) {
        n = read (pFdOut, buffer,
          LINE_MAX);
        if (n == 0 || (n < 0
          && errno != EINTR)) break;
        for (i = 0; i < n; i++)
          putchar (buffer [i]);
    }
    close (pFdOut);

    /* Уничтожение мьютексов */
    for (i = 0; i < nMudr; i++) {
        pthread_mutex_destroy (
         &mtxFork [i]);
    }

    /* Выдача сводной информации */
    {
        int full_eating_time = 0;
        int full_waiting_time = 0;
        int full_thinking_time = 0;
        for (i = 1; i <= nMudr; i++) {
            struct mudrec *this =
              &kafedra [i – 1];

            full_eating_time +=
              this->eat_time;
            full_waiting_time +=
              this->wait_time;
            full_thinking_time +=
              this->think_time;

            if (this->count > 0) {
            float count =
              this->count;
            float think_time =
              this->think_time /
                count;
            float eat_time =
              this->eat_time /
                count;
            float wait_time =
              this->wait_time / 
                count;

            printf (
              "%s: ел %d раз в "
              "среднем: думал=%.1f "
              "ел=%.1f ждал=%.1f "
              "(максимум %d)\n", 
              this->name,
              this->count, 
              think_time,eat_time,
              wait_time, 
              this->max_wait_time);
            } else
            printf ("%s: не поел\n",
              this->name);
        } /* for */

        {
        float total_time = (
            full_eating_time + 
            full_waiting_time + 
            full_thinking_time) /
             (float) nMudr;
printf("Среднее число одновременно"
       " едящих = %.3f\n", 
       full_eating_time / total_time);
printf("Среднее число одновременно"
       " ждущих = %.3f\n",
       full_waiting_time / total_time);
        }
        }
        /* Выдача сводной информации */

    free (mtxFork);
    free (kafedra);

    /* Сообщим об окончании работы. */
    printf ("Конец обеда\n");

    return 0;
}
Листинг 2.11. Многопотоковый вариант решения задачи об обедающих философах с использованием мьютексов.