Однопоточные программы C содержат два основных класса данных: локальные и глобальные данные. Для многопоточных программ C добавляется третий класс: данные потока. Они похожи на глобальные данные, за исключением того, что они являются собственными для потока.
Данные потока являются единственным способом определения и обращения к данным, которые принадлежат отдельному потоку. Каждый элемент данных потока связан с ключом, который является глобальным для всех потоков процесса. Используя ключ, поток может получить доступ к указателю (void *), который поддерживается только для этого потока.
Функция pthread_keycreate() используется для выделения ключа, который используется для идентифицикации данных некоторого потока в процессе. Ключ глобален для всех потоков, и все потоки в начале содержат значение ключа NULL.
pthread_keycreate() вызывается отдельно для каждого ключа перед его использованием. При этом нет никакой неявной синхронизации. Как только ключ будет создан, каждый поток может связать значение с ключом. Значения являются специфичными для потока и поддерживаются для каждого потока независимо. Связывание ключа с потоком удаляется, когда поток заканчивается, при этом ключ должен быть создан с функцией деструктора. Прототип функции:
pthread_key_t key;
int ret;
/* создание ключа без деструктора */
ret = pthread_key_create(&key, NULL);
/* создание ключа с деструктором */
ret = pthread_key_create(&key, destructor);
pthread_keycreate() возвращает 0 при успешном завершении, или любое другое значение при возникновении ошибки.
Функция pthread_keydelete() используется, чтобы уничтожить существующий ключ данных для определенного потока. Любая выделенная память, связанная с ключом, может быть освобождена, потому что ключ был удален. Ссылка на эту память возвратит ошибку.
Прототип pthread_keydelete():
pthread_key_t key;
int ret;
/* key был создан ранее */
ret = pthread_key_delete(key);
Программист должен сам нести ответственность за освобождение любых ресурсов, выделенных потоку, перед вызовом функции удаления. Эта функция не вызывает деструктор.
pthread_keydelete() возвращает 0 после успешного завершения, или любое другое значение в случае ошибки.
Функция pthread_setspecific() используется, чтобы установить связку между потоком и указанным ключом данных для потока. Прототип функции:
pthread_key_t key;
void *value;
int ret;
/* key был создан ранее */
ret = pthread_setspecific(key, value);
Чтобы получить привязку ключа для вызывающего потока, используется функция pthread_getspecific(). Полученное значение сохраняется в переменной value. Прототип функции:
pthread_key_t key;
void *value;
/* key был создан ранее */
value = pthread_getspecific(key);
...
while (write(fd, buffer, size) == -1) {
if (errno != EINTR) {
fprintf(mywindow, "%s\n", strerror(errno));
exit(1);
}
}
...
}
Ссылки на errno должны получить код системной ошибки из процедуры, вызванной этим конкретным потоком, а не некоторым другим. Поэтому ссылки на errno в одном потоке относятся к отдельной области памяти, чем ссылки на errno в других потоках. Переменная mywindow предназначена для обращения к потоку stdio, связанному с окном, которое является частным объектом потока. Также как и errno, ссылки на mywindow в одном потоке должны обращаться к отдельной области памяти (и, в конечном счете, к различным окнам). Единственное различие между этими переменными состоит в том, что библиотека потоков реализует раздельный доступ для errno, а программист должен сам реализовать это для mywindow. Следующий пример показывает, как работают ссылки на mywindow. Препроцессор преобразовывает ссылки на mywindow в вызовы процедур mywindow. Эта процедура в свою очередь вызывает pthread_getspecific(), передавая ему глобальную переменную mywindow_key (это действительно глобальная переменная) и выходной параметр win, который принимает идентификатор окна для этого потока.
Следующий фрагмент кода:
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 )
...
}
Теперь можно устанавливать собственные данные потока:
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);
}
Следующий шаг состоит в выделении памяти для элемента данных вызывающего потока. После выделения памяти выполняется вызов процедуры create_window, которая устанавливает окно для потока и выделяет память для переменной win, которая ссылается на окно. Наконец, выполняется вызов pthread_setspecific(), который связывает значение win с ключом. После этого, как только поток вызывает pthread_getspecific(), передавая глобальный ключ, он получает некоторое значение. Это значение было связано с этим ключом в вызывающем потоке, когда он вызвал pthread_setspecific(). Когда поток заканчивается, выполняются вызовы функций деструкторов, которые были настроены в pthread_key_create(). Каждая функция деструктора вызывается, если завершившийся поток установил значение для ключа вызовом pthread_setspecific().
Функция pthread_self() вызывается для получения ID вызывающего ее потока:
pthread_t tid;
tid = pthread_self();
pthread_t tid1, tid2;
int ret;
ret = pthread_equal(tid1, tid2);
Функция pthread_once() используется для вызова процедуры инициализации потока только один раз. Последующие вызовы не оказывают никакого эффекта. Пример вызова функции:
void (*init_routine)(void));
int ret;
ret = sched_yield();
Функция pthread_setschedparam() используется, чтобы изменить приоритет существующего потока. Эта функция никоим образом не влияет на дисциплину диспетчеризации:
const struct sched_param *param);
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, ¶m);
Функция
struct schedparam *param)
Пример вызова функции:
pthread_t tid;
sched_param param;
int priority;
int policy;
int ret;
/* параметры диспетчеризации нужного потока */
ret = pthread_getschedparam (tid, &policy, ¶m);
/* sched_priority содержит приоритет потока */
priority = param.sched_priority;
Поток, как и процесс, может принимать различные сигналы:
#include <signal.h>
int sig;
pthread_t tid;
int ret;
ret = pthread_kill(tid, sig);
Если sig имеет значение 0, выполняется проверка ошибок, но сигнал реально не посылается. Таким образом можно проверить правильность tid. Функция возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.
Функция pthread_sigmask() может использоваться для изменения или получения маски сигналов вызывающего потока:
#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); /* снятие блокировки */
pthread_sigmask() возвращает 0 в случае успешного завершения, или другое значение в случае ошибки.