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

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


Данные нитей

Во многих приложениях необходимо работать с данными, относящимися к отдельным нитям. Например, для выполнения команды grep, если на каждый файл отводится одна нить, необходимы обработчики файлов для конкретных нитей и список найденных строк. В этих целях библиотека нитей содержит интерфейс данных для конкретных нитей.

Данные для конкретных нитей можно просмотреть в виде двумерного массива значений, причем по строкам размещены ключи, а по столбцам - ИД нитей. Ключ данных конкретной нити - это объект со скрытой реализацией типа pthread_key_t. Один и тот же ключ может применяться во всех нитях процесса. Хотя ключи для всех нитей один и те же, с этими ключами в различных нитях связаны различные данные. Данные конкретных нитей имеют тип "указатель на void". Благодаря этому они могут указывать на любой тип данных, например, на динамические строки или структуры.

На следующем рисунке в нити T2 значение данных 12 связано с ключом K3. В другой нити, T4, с тем же ключом связано значение 2.

Табл. 11-1. Массив данных нитей


Нити
T1 T2 T3 T4
Ключи K1 6 56 4 1
K2 87 21 0 9
K3 23 12 61 2
K4 11 76 47 88

Следующие разделы посвящены данным нитей:

Создание и уничтожение ключей

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

Создание ключа

Для создания ключа следует вызвать функцию pthread_key_create. Эта функция возвращает ключ. Значения данных, соответствующих ключу, при этом равны NULL для всех нитей, в том числе и для тех нитей, которые еще не созданы.

Пусть, например, есть две нити (А и В). Нить А выполняет в хронологическом порядке следующие операции:

  1. Создает ключ данных К.

    Нити А и В могут использовать ключ К. В обеих нитях этому ключу соответствует значение NULL.

  2. Создает нить С.

    Нить С также может использовать ключ К. В нити С этому ключу соответствует значение NULL.

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

Функцию pthread_key_create можно вызвать только один раз, иначе будет создано два разных ключа. Например, рассмотрим следующий фрагмент программы:

/* глобальная переменная */
static pthread_key_t theKey;
 
/* нить A */
...
pthread_key_create(&theKey, NULL);   /* 1-й вызов */
...
 
/* нить B */
...
pthread_key_create(&theKey, NULL);   /* 2-й вызов */
...

Нити А и В выполняются параллельно, но первый вызов происходит раньше второго. При первом вызове программа создает ключ K1 и помещает его в переменную theKey. При втором вызове программа создает другой ключ K2 и помещает его в ту же переменную theKey, поэтому ключ K1 уничтожается. В результате нить A будет работать с ключом K2, считая его ключом K1. Таких ситуаций следует избегать по двум причинам:

Обеспечить уникальность ключей можно двумя способами:

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

Деструктор

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

Ключи данных можно использовать, например, для динамически выделенных буферов. Для освобождения буферов после завершения работы нити следует вызвать деструктор; можно применять функцию free:

pthread_key_create(&key, free);

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

typedef struct {
        FILE *stream;
        char *buffer;
} data_t;
...

void destructor(void *data)
{
        fclose(((data_t *)data)->stream);
        free(((data_t *)data)->buffer);
        free(data);
        *data = NULL;
}

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

Уничтожение ключа

Уничтожить ключ данных можно с помощью функции pthread_key_delete. Эта функция освобождает память, отведенную под ключ, только в случае, если с ключом не связаны данные. Данные считаются связанными с ключом, если хотя бы одно значение не равно NULL. Функция pthread_key_delete не вызывает деструктор для каждой нити с данными. Для уничтожения ключа необходимо, чтобы с этим ключом не были связаны данные; обеспечить это - задача программиста.

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

/* плохой пример! */
pthread_key_t key;
 
while (pthread_key_create(&key, NULL))
        pthread_key_delete(key);

Работа с данными нитей

Для доступа к данным нитей служат функции pthread_getspecific и pthread_setspecific. Первая из них считывает значение, связанное с указанным в параметре ключом и относящееся к указанной нити; вторая устанавливает это значение.

Изменение значений

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

private_data = malloc(...);
pthread_setspecific(key, private_data);

При установке нового значения предыдущее значение теряется. Например, в приведенном ниже фрагменте программы значение указателя old теряется, а память, на которую ссылается этот указатель, превращается в "мусор":

pthread_setspecific(key, old);
...
pthread_setspecific(key, new);

Ответственность за сохранение старых указателей, т.е. за образование "мусора", несет программист. Например, можно реализовать процедуру swap_specific следующим образом:

int swap_specific(pthread_key_t key, void **old_pt, void *new)
{
        *old_pt = pthread_getspecific(key);
        if (*old_pt == NULL)
                return -1;
        else
                return pthread_setspecific(key, new);
}

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

Замечания о деструкторах

При работе с динамическими данными нитей программист должен предусмотреть для каждого вызова pthread_key_create вызов деструктора. Программист должен также присвоить указателю на освобожденную память значение NULL. В противном случае возможен вызов деструктора с недопустимыми параметрами. Например:

pthread_key_create(&key, free);
...

...
private_data = malloc(...);
pthread_setspecific(key, private_data);
...

/* плохой пример! */
...
pthread_getspecific(key, &data);
free(data);
...

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

/* правильный код */
...
pthread_getspecific(key, &data);
free(data);
pthread_setspecific(key, NULL);
...

При завершении работы нити деструктор не вызывается, поскольку все данные уже освобождены.

Работа с другими типами данных

Структура данных позволяет хранить значения, не являющиеся указателями - например, числа типа integer. Это не рекомендуется по нескольким причинам, в том числе следующим:

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

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

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

Дополнительные функции работы с нитями

Разовая инициализация

Расширенные атрибуты

Создание сложных объектов синхронизации

Список дополнительных функций для работы с нитями


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