Опубликован: 06.12.2004 | Уровень: специалист | Доступ: платный
Лекция 1:

Потоки управления

Лекция 1: 1234567 || Лекция 2 >

Работа с индивидуальными данными потоков управления

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

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

Для решения проблемы однократного выполнения инициализирующих действий в многопотоковой среде стандарт POSIX-2001 предлагает функцию pthread_once() (см. листинг 1.19).

#include <pthread.h>

pthread_once_t once_control = 
    PTHREAD_ONCE_INIT;

int pthread_once (
    pthread_once_t *once_control_ptr,
    void (*init_routine) (void));
Листинг 1.19. Описание функции pthread_once().

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

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

За создание и удаление ключа индивидуальных данных потоков управления, согласно стандарту POSIX-2001, отвечают функции pthread_key_create() и pthread_key_delete() (см. листинг 1.20).

#include <pthread.h>

int pthread_key_create (
    pthread_key_t *key_ptr, 
    void (*destructor) (void *));

int pthread_key_delete (
    pthread_key_t key);
Листинг 1.20. Описание функций pthread_key_create() и pthread_key_delete().

Функция pthread_key_create() создает новый ключ, которым могут воспользоваться все входящие в процесс потоки управления, и, в соответствии со "штабной дисциплиной", помещает его по указателю key_ptr. Сразу после создания ключа для всех потоков в качестве индивидуальных данных с ним ассоциируется значение NULL. (Аналогично, после создания потока управления он не имеет индивидуальных данных, поэтому со всеми доступными ему ключами также ассоциированы пустые указатели.)

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

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

Для выборки и изменения ассоциированных с ключом key индивидуальных данных вызывающего потока управления предназначены функции pthread_getspecific() и pthread_setspecific() (см. листинг 1.21).

#include <pthread.h>

void *pthread_getspecific (
    pthread_key_t key);

int pthread_setspecific (
    pthread_key_t key, 
    const void *value);
Листинг 1.21. Описание функций pthread_getspecific() и pthread_setspecific().

Функция pthread_getspecific() возвращает индивидуальные данные в качестве результата; функция pthread_setspecific() ассоциирует с ключом key значение аргумента value.

На листинге 1.22 показана программа, использующая стандартную схему создания ключа и манипулирования индивидуальными данными потоков управления.

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа запоминает в качестве индивидуальных данных */
/* потока управления время начала активных операций     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
static pthread_key_t data_key;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Деструктор индивидуальных данных, в роли которых     */
/* выступает указатель на структуру типа timeval.     */
/* Поскольку она не содержит указателей, достаточно    */
/* освободить занимаемую ею память     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void data_destructor (void *p) {
    free (p);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция создания ключа индивидуальных данных,    */
/* ассоциирующая с ним деструктор, освобождающий память */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void create_data_key (void) {
    (void) pthread_key_create (&data_key, data_destructor);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция инициализации индивидуальных данных.     */
/* Запрашивает астрономическое время     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_func (void) {
    struct timeval *tmvl_ptr;

    /* Запомним астрономическое время начала операций     */
    /* потока управления             */
    if ((tmvl_ptr =
        (struct timeval *) malloc (sizeof (struct timeval))) 
            == NULL) {
        return (NULL);
    }
    (void) gettimeofday (tmvl_ptr, NULL);

/* Создадим ключ индивидуальных данных, перепоручив     */
    /* вызов pthread_key_create() функции pthread_once()     */
    (void) pthread_once (&key_once, create_data_key);

    (void) pthread_setspecific (data_key, tmvl_ptr);
return (tmvl_ptr);
}

/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() вызывает функцию инициализации    */
/* и запрашивает индивидуальные данные потока     */
/* управления                        */
/* * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
    struct timeval *tmvl_ptr;

    if (start_func () == NULL) {
        return (1);
    }

    if ((tmvl_ptr =
            (struct timeval *) pthread_getspecific (data_key)) 
                != NULL) {
        printf ("Время начала операций потока управления: "
                    "%ld сек, %ld мсек\n",
            tmvl_ptr->tv_sec, tmvl_ptr->tv_usec);
    } else {
        printf ("Отсутствуют индивидуальные данные потока "
                    "управления.\n");
        printf ("Время начала операций неизвестно\n");
        return (2);
    }

    return 0;
}
Листинг 1.22. Пример программы, формирующей и опрашивающей индивидуальные данные потоков управления.

Результат работы этой программы может выглядеть так, как показано на листинге 1.23.

Время начала операций потока управления: 
1075707670 сек, 584737 мсек
Листинг 1.23. Возможные результаты работы программы, формирующей и опрашивающей индивидуальные данные потоков управления.
Лекция 1: 1234567 || Лекция 2 >
Максим Соколов
Максим Соколов
Россия, г. Москва
фыв ыва
фыв ыва
Афганистан