Однопоточные программы на 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 . Вызывающий процесс должен гарантировать, что хранение и доступ к этому ключу синхронизированы. Чтобы освободить ранее выделенную память, может использоваться дополнительная функция удаления - деструктор. Если ключ имеет непустой указатель на функцию деструктора, и поток имеет непустое значение ключа, функция деструктора вызывается для значения, связанного с потоком, после его завершения. Порядок, в котором вызываются функции деструктора, может быть произвольным; 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_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, ¶m);
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, ¶m);
/* 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 , который должен быть потоком в пределах того же самого процесса, что и вызывающий поток. Аргумент 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 - в случае успешного завершения - или другое значение - в случае ошибки.