[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Стыковка нитей

Стыковка с нитью означает ожидание ее завершения. Это можно рассматривать как один из способов применения переменных условия.

Более подробно объединение нитей описывается в следующих разделах:

Ожидание завершения нити

Простой механизм, позволяющий нити ожидать завершения другой нити, предоставляет процедура pthread_join. Более сложные ситуации, например, ожидание завершения нескольких нитей, программист может реализовать с помощью переменных условия. Дополнительная информация приведена в разделе Синхронизация нитей с помощью условных переменных.

Вызов функции pthread_join

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

В приведенной ниже таблице описаны два возможных случая вызова процедуры pthread_join из нити, в зависимости от состояния и значения атрибута detachstate (автономности) целевой нити.



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

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

Стыковка нескольких нитей с одной

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

Пример стыковки

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

#include <pthread.h>    /* первый включаемый файл - pthread.h */
#include <stdio.h>      /* поддержка функции printf()         */

void *Thread(void *string)
{
      int i;
      /* выводит пять сообщений и завершает работу */
      for (i=0; i<5; i++)
              printf("%s\n", (char *)string);
      pthread_exit(NULL); }

int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_attr_t attr;
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;

        /* создает правильный атрибут */
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr,
                PTHREAD_CREATE_UNDETACHED);

        /* создание обеих нитей */
        rc = pthread_create(&e_th, &attr, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, &attr, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_attr_destroy(&attr);

        /* стыкует нити */
        pthread_join(e_th, NULL);
        pthread_join(f_th, NULL);
 
        pthread_exit(NULL);
}

Возврат информации из нити

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

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

/* дочерняя нить для просмотра */
...
buffer = malloc(...);
        /* ищет в файле строки, совпадающие с шаблоном 
           поиска, и заносит их в буфер             */
return (buffer);

/* главная нить   */
...
for (/* для каждой созданной нити */) {
        void *buf;
        pthread_join(thread, &buf);
        if (buf != NULL) {
                /* печатает все строки из буфера, указывая
                        перед каждой строкой имя файла нити */
                free(buf);
        }
}
...

При принудительном завершении целевой нити процедура pthread_join возвращает в указателе -1. Поскольку указатель не может быть равен -1, это означает, что нить была завершена принудительно.

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

Кроме того, следует соблюдать осторожность при возврате указателя на область динамической памяти, отведенную для нескольких нитей. Рассмотрим следующий фрагмент кода:

void *returned_data;
...
pthread_join(target_thread, &returned_data);
/* считывание информации из области returned_data */
free(returned_data);

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

free(returned_data);

следует заменить на следующую (в предположении, что начальное значение переменной flag равно 0)

/* установка блокировки - критический фрагмент кода, другие нити
                не должны параллельно выполнять этот код */
if (!flag) {
        free(returned_data);
        flag = 1;
}
/* снятие блокировки - критический фрагмент окончен */

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

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

Связанная информация

Рекомендации по созданию программ с нитями

Обзор синхронизации

Использование взаимных блокировок

Использование условных переменных

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


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]