next up previous contents
Next: Остановка потока. Up: Потоки (threads). Previous: Отделение потока.   Contents

Работа с ключами потока.

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

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

Функция pthread_keycreate() используется для выделения ключа, который используется для идентифицикации данных некоторого потока в процессе. Ключ глобален для всех потоков, и все потоки в начале содержат значение ключа NULL.

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

int pthread_key_create(pthread_key_t *key, void(*destructor)(void *));
Пример использования:

#include <pthread.h>

pthread_key_t key;

int ret;

/* создание ключа без деструктора */

ret = pthread_key_create(&key, NULL);

/* создание ключа с деструктором */

ret = pthread_key_create(&key, destructor);

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

pthread_keycreate() возвращает 0 при успешном завершении, или любое другое значение при возникновении ошибки.

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

Прототип pthread_keydelete():

int pthread_key_delete(pthread_key_t key);
Пример использования функции:

#include <pthread.h>

pthread_key_t key;

int ret;

/* key был создан ранее */

ret = pthread_key_delete(key);

Как только ключ будет удален, любая ссылка на него через pthread_setspecific() или pthread_getspecific() приведет к ошибке EINVAL.

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

pthread_keydelete() возвращает 0 после успешного завершения, или любое другое значение в случае ошибки.

Функция pthread_setspecific() используется, чтобы установить связку между потоком и указанным ключом данных для потока. Прототип функции:

int pthread_setspecific(pthread_key_t key, const void *value);
Пример вызова:

#include <pthread.h>

pthread_key_t key;

void *value;

int ret;

/* key был создан ранее */

ret = pthread_setspecific(key, value);

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

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

int pthread_getspecific(pthread_key_t key);
Пример:

#include <pthread.h>

pthread_key_t key;

void *value;

/* key был создан ранее */

value = pthread_getspecific(key);

Рассмотрим следующий код:

body() {

  ...

  while (write(fd, buffer, size) == -1) {

    if (errno != EINTR) {

       fprintf(mywindow, "%s\n", strerror(errno));

       exit(1);

    }

  }

  ...

}

Этот код может быть выполнен любым числом потоков, но он содержит ссылки на две глобальных переменных, errno и mywindow, которые в действительности должны быть ссылками на объекты, являющиеся частными для каждого потока.

Ссылки на errno должны получить код системной ошибки из процедуры, вызванной этим конкретным потоком, а не некоторым другим. Поэтому ссылки на errno в одном потоке относятся к отдельной области памяти, чем ссылки на errno в других потоках. Переменная mywindow предназначена для обращения к потоку stdio, связанному с окном, которое является частным объектом потока. Также как и errno, ссылки на mywindow в одном потоке должны обращаться к отдельной области памяти (и, в конечном счете, к различным окнам). Единственное различие между этими переменными состоит в том, что библиотека потоков реализует раздельный доступ для errno, а программист должен сам реализовать это для mywindow. Следующий пример показывает, как работают ссылки на mywindow. Препроцессор преобразовывает ссылки на mywindow в вызовы процедур mywindow. Эта процедура в свою очередь вызывает pthread_getspecific(), передавая ему глобальную переменную mywindow_key (это действительно глобальная переменная) и выходной параметр win, который принимает идентификатор окна для этого потока.

Следующий фрагмент кода:

thread_key_t mywin_key;

FILE *_mywindow(void) {

  FILE *win;

  pthread_getspecific(mywin_key, &win);

  return(win);

}

 

#define mywindow _mywindow()

 

void routine_uses_win( FILE *win) {

  ...

  }

 

void thread_start(...) {

  ...

  make_mywin();

  ...

  routine_uses_win( mywindow )

  ...

}

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

Теперь можно устанавливать собственные данные потока:

void make_mywindow(void) {

  FILE **win;

  static pthread_once_t mykeycreated = PTHREAD_ONCE_INIT;

  pthread_once(&mykeycreated, mykeycreate);

  win = malloc(sizeof(*win));

  create_window(win, ...);

  pthread_setspecific(mywindow_key, win);

}

 

void mykeycreate(void) {

  pthread_keycreate(&mywindow_key, free_key);

}

 

void free_key(void *win) {

  free(win);

}

Сначала нужно получить уникальное значение для ключа mywin_key. Этот ключ используется, чтобы идентифицировать класс данных потока. Таким образом, первый поток, который вызовет make_mywin, вызывает также pthread_keycreate(), который присваивает своему первому аргументу уникальный ключ. Второй аргумент - функция деструктора, которая используется, чтобы освободить экземпляр определенного элемента данных в потоке, как только поток завершается.

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

Функция pthread_self() вызывается для получения ID вызывающего ее потока:

#include <pthread.h>

pthread_t tid;

tid = pthread_self();

Функция pthread_equal() вызывается для сравнения идентификаторов двух потоков:

#include <pthread.h>

pthread_t tid1, tid2;

int ret;

ret = pthread_equal(tid1, tid2);

Как и другие функции сравнения, pthread_equal() возвращает значение, отличное от нуля, когда tid1 и tid2 равны; иначе возвращается 0. Если tid1 или tid2 - недействительный идентификатор потока, результат функции не определен.

Функция pthread_once() используется для вызова процедуры инициализации потока только один раз. Последующие вызовы не оказывают никакого эффекта. Пример вызова функции:

int pthread_once(pthread_once_t *once_control,

    void (*init_routine)(void));

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

#include <sched.h>

int ret;

ret = sched_yield();

После успешного завершения sched_yield() возвращает 0. Если возвращается -1, то системная переменная errno устанавливается на код ошибки.

Функция pthread_setschedparam() используется, чтобы изменить приоритет существующего потока. Эта функция никоим образом не влияет на дисциплину диспетчеризации:

int pthread_setschedparam(pthread_t tid, int policy,

       const struct sched_param *param);

Использование функции:

#include <pthread.h>

pthread_t tid;

int ret;

struct sched_param param;

int priority;

/* sched_priority указывает приоритет потока */

sched_param.sched_priority = priority;

/* единственный поддерживаемый алгоритм диспетчера*/

policy = SCHED_OTHER;

/* параметры диспетчеризации требуемого потока */

ret = pthread_setschedparam(tid, policy, &param);

pthread_setschedparam() возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.

Функция

int pthread_getschedparam(pthread_t tid, int policy,

      struct schedparam *param)

позволяет получить приоритет любого существующего потока.

Пример вызова функции:

#include <pthread.h>

pthread_t tid;

sched_param param;

int priority;

int policy;

int ret;

/* параметры диспетчеризации нужного потока */

ret = pthread_getschedparam (tid, &policy, &param);

/* sched_priority содержит приоритет потока */

priority = param.sched_priority;

pthread_getschedparam() возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.

Поток, как и процесс, может принимать различные сигналы:

#include <pthread.h>

#include <signal.h>

int sig;

pthread_t tid;

int ret;

ret = pthread_kill(tid, sig);

pthread_kill() посылает сигнал sig потоку, указанному tid. tid должен быть потоком в пределах того же самого процесса, что и вызывающий поток. Аргумент sig должен быть действительным сигналом некоторого типа, определенного для функции signal() в файле < signal.h>.

Если sig имеет значение 0, выполняется проверка ошибок, но сигнал реально не посылается. Таким образом можно проверить правильность tid. Функция возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.

Функция pthread_sigmask() может использоваться для изменения или получения маски сигналов вызывающего потока:

int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
Пример вызова функции:

#include <pthread.h>

#include <signal.h>

int ret;

sigset_t old, new;

ret = pthread_sigmask(SIG_SETMASK, &new, &old); /* установка новой маски */

ret = pthread_sigmask(SIG_BLOCK, &new, &old); /* блокирование маски */

ret = pthread_sigmask(SIG_UNBLOCK, &new, &old); /* снятие блокировки */

how определяет режим смены маски. Он принимает значения следующих констант:

SIG_SETMASK
 
-
Заменяет текущую маску сигналов новой, при этом new указывает новую маску сигналов.
SIG_BLOCK
 
-
Добавляет новую маску сигналов к текущей, при этом new указывает множество блокируемых сигналов.
SIG_UNBLOCK
 
-
Удаляет new из текущей маски сигналов, при этом new указывает множество сигналов для снятия блокировки.
Если значение new равно NULL, значение how не играет роли и маска сигналов потока не изменяется. Чтобы узнать о блокированных в настоящее время сигналах, аргумент new устанавливают в NULL. Переменная old указывает, где хранится прежняя маска сигналов, если ее значение не равно NULL.

pthread_sigmask() возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.


next up previous contents
Next: Остановка потока. Up: Потоки (threads). Previous: Отделение потока.   Contents
2003-12-09