Напомним, уточним и дополним определения, которые были даны в курсе [1] применительно к
После того, как fork(), он считается fork().
До завершения
Большинство errno, ассоциированные с
pthread_t, который трактуется в стандарте POSIX-2001 как абстрактный. В частности, для него определен метод сравнения значений на равенство.
Всем malloc(), прямоадресуемая память, полученная от системно-зависимых функций,
PTHREAD_SCOPE_SYSTEM ( PTHREAD_SCOPE_PROCESS (
SIGSEGV
pthread_attr_t, внутренняя структура значений которого скрыта от приложений. Смысл введения
Обратим внимание на следующее обстоятельство, важное для реализации многопотоковых приложений. Иногда
Операции с
В таком порядке они и будут рассматриваться далее. Отметим, что стандарт POSIX-2001 относит их к необязательной части, именуемой, как нетрудно догадаться, "
fork() / exec() создающий pthread_create() posix_spawn() ) для порождения
Согласно стандарту POSIX-2001, accept(), connect(), msgrcv(), msgsnd(), pause(), read(), sleep(), wait(), write() и сходных с ними по поведению. Допускается существование подобных точек и в других, также оговоренных в стандарте POSIX функциях – printf(), scanf(), semop() и т.п.
Следуя классическому принципу "познай самого себя", описание функций, обслуживающих pthread_self(), возвращающей в качестве результата
#include <pthread.h> pthread_t pthread_self (void);
Выше мы отмечали, что тип pthread_t трактуется стандартом POSIX-2001 как абстрактный. На уровне языка C он может быть представлен, например, структурой. Для работы со значениями типа pthread_t предусмотрены два метода: присваивание и сравнение на равенство, реализуемое функцией pthread_equal() (см. листинг 1.2).
#include <pthread.h>
int pthread_equal (pthread_t t1,
pthread_t t2);
Если значения аргументов t1 и t2 равны, результат функции pthread_equal() отличен от нуля.
pthread_attr_init() и pthread_attr_destroy() (см. листинг 1.3).
#include <pthread.h>
int pthread_attr_init (
pthread_attr_t *attr);
int pthread_attr_destroy (
pthread_attr_t *attr);
Функция pthread_attr_init() инициализирует attr, подразумеваемыми значениями для всех индивидуальных
Функция pthread_attr_destroy() разрушает заданный
Структура
#include <pthread.h>
int pthread_attr_getstack (
const pthread_attr_t *restrict attr,
void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack (
pthread_attr_t *attr, void *stackaddr,
size_t stacksize);
Размер стека должен составлять не менее PTHREAD_STACK_MIN, начальный адрес – должным образом выровнен. Память, отведенная под стек, должна быть доступна на чтение и запись.
Функция pthread_attr_getstack() помещает атрибуты стека по указателям stackaddr и stacksize. Это – проявление единообразной для семейства функций pthread*(), обслуживающих
Подобная дисциплинированность является похвальной, но вынужденной. Ее причина – в разделении данных между malloc() ) и передать функции указатели на них.
Для опроса и изменения размера pthread_attr_getguardsize() и pthread_attr_setguardsize() (см. листинг 1.5).
#include <pthread.h>
int pthread_attr_getguardsize (
const pthread_attr_t *restrict attr,
size_t *restrict guardsize);
int pthread_attr_setguardsize (
pthread_attr_t *attr,
size_t guardsize);
Если значение аргумента guardsize функции pthread_attr_setguardsize() равно нулю, при *attr guardsize также становятся новыми значениями одноименного атрибута, однако являются лишь указанием операционной системе; реальный размер
Приложение, соответствующее стандарту POSIX, должно использовать значения guardsize, кратные конфигурационной константе PAGESIZE, которая одновременно является подразумеваемым значением данного атрибута.
Если приложение посредством функции pthread_attr_setstack() взяло на себя управление стеками guardsize игнорируется, операционная система не отводит
Отметим, что, в зависимости от ситуации, приложениям есть смысл как отказываться от
Стандартом POSIX-2001 предусмотрена группа <sched.h>. Центральную роль среди них играет структура типа sched_param, которая должна содержать по крайней мере поле int sched_priority; /* Приоритет планирования при выполнении потока */
SCHED_SPORADIC ( _POSIX_SPORADIC_SERVER и/или _POSIX_THREAD_SPORADIC_SERVER, а в структуре sched_param должны присутствовать следующие дополнительные поля
int sched_ss_low_priority;
/* Нижняя граница приоритета */
/* планирования сервера */
/* спорадических событий */
struct timespec sched_ss_repl_period;
/* Период пополнения бюджета */
/* спорадического сервера */
struct timespec sched_ss_init_budget;
/* Начальный бюджет */
/* спорадического сервера */
int sched_ss_max_repl;
/* Максимальное число */
/* ждущих операций */
/* пополнений бюджета */
/* спорадического сервера */
Для опроса и установки pthread_attr_getschedparam() и pthread_attr_setschedparam() (см. листинг 1.6).
#include <pthread.h>
int pthread_attr_getschedparam (
const pthread_attr_t *restrict attr,
struct sched_param *restrict param);
int pthread_attr_setschedparam (
pthread_attr_t *restrict attr,
const struct sched_param *restrict param);
SCHED_FIFO ( SCHED_RR ( SCHED_OTHER ( SCHED_SPORADIC ( pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy() (см. листинг 1.7).
#include <pthread.h>
int pthread_attr_getschedpolicy (
const pthread_attr_t *restrict attr,
int *restrict policy);
int pthread_attr_setschedpolicy (
pthread_attr_t *attr, int policy);
Описанный выше PTHREAD_SCOPE_SYSTEM и PTHREAD_SCOPE_PROCESS, обслуживают функции pthread_attr_getscope() и pthread_attr_setscope() (см. листинг 1.8).
#include <pthread.h>
int pthread_attr_getscope (
const pthread_attr_t *restrict attr,
int *restrict contentionscope);
int pthread_attr_setscope (
pthread_attr_t *attr,
int contentionscope);
При PTHREAD_INHERIT_SCHED или PTHREAD_EXPLICIT_SCHED inheritsched, могут pthread_attr_getinheritsched() и pthread_attr_setinheritsched() (см. листинг 1.9).
#include <pthread.h>
int pthread_attr_getinheritsched (
const pthread_attr_t *restrict attr,
int *restrict inheritsched);
int pthread_attr_setinheritsched (
pthread_attr_t *attr,
int inheritsched);
pthread_attr_getdetachstate() и pthread_attr_setdetachstate() (см. листинг 1.10).
#include <pthread.h>
int pthread_attr_getdetachstate (
const pthread_attr_t *attr,
int *detachstate);
int pthread_attr_setdetachstate (
pthread_attr_t *attr,
int detachstate);
Напомним, что значение этого PTHREAD_CREATE_DETACHED или PTHREAD_CREATE_JOINABLE ) определяет, будет ли PTHREAD_CREATE_JOINABLE.
Значения
#include <pthread.h>
int pthread_getschedparam (
pthread_t thread, int *restrict policy,
struct sched_param *restrict param);
int pthread_setschedparam (
pthread_t thread, int policy,
const struct sched_param *param);
Отметим две тонкости, связанные с функцией pthread_setschedparam(). Во-первых, возможно, что для ее успешного вызова SCHED_SPORADIC ) (как, впрочем, и саму эту
Если требуется изменить лишь pthread_setschedprio() (см. листинг 1.12), которая, правда, является новой и в исторически сложившихся реализациях может отсутствовать.
#include <pthread.h>
int pthread_setschedprio (
pthread_t thread, int prio);
Сходную направленность, но более глобальный характер имеют функции pthread_getconcurrency() и pthread_setconcurrency() (см. листинг 1.13), позволяющие опросить и изменить
#include <pthread.h> int pthread_getconcurrency (void); int pthread_setconcurrency (int new_level);
По умолчанию операционная система предоставляет возможность параллельно проявлять активность некоему "достаточному числу" new_level функции pthread_setconcurrency(). Впрочем, с точки зрения операционной системы это всего лишь просьба или рекомендация; стандарт не специфицирует реально устанавливаемый уровень.
Нулевое значение аргумента new_level означает переход к подразумеваемому pthread_setconcurrency() ранее не вызывалась.
Функция pthread_getconcurrency() в качестве результата возвращает значение pthread_setconcurrency(). Если такового не было, выдается нуль.
Отметим, что изменение
К числу pthread_getcpuclockid() (см. листинг 1.14).
#include <pthread.h>
#include <time.h>
int pthread_getcpuclockid (
pthread_t thread_id,
clockid_t *clock_id);
Еще один pthread_sigmask() (см. листинг 1.15) – аналога рассмотренной в курсе [1] функции sigprocmask().
#include <signal.h>
int pthread_sigmask (
int how, const sigset_t *restrict set,
sigset_t *restrict oset);
На листинге 1.16 приведен пример программы, использующей большинство описанных выше функций для опроса и изменения
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа опрашивает атрибуты потоков управления */
/* и изменяет некоторые из них */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <assert.h>
int main (void) {
pthread_t ct_id; /* Идентификатор текущего потока управления */
pthread_attr_t patob; /* Атрибутный объект для создания потоков управления */
int res; /* Переменная для запоминания результатов "потоковых" функций */
void *stackaddr; /* Начало стека как атрибут потока управления */
size_t atrsize; /* Размеры как атрибуты потока управления */
/* Структура с параметрами планирования */
struct sched_param shdprm;
char *spname; /* Названия политики планирования, области */
/* планирования конкуренции и т.п. */
printf ("Идентификатор текущего потока управления: %lx\n",
(ct_id = pthread_self ()));
if ((errno = pthread_attr_init (patob)) != 0) {
perror ("PTHREAD_ATTR_INIT");
return (errno);
}
printf ("Значения, установленные системой "
"в атрибутном объекте\n");
if ((errno = pthread_attr_getstack (patob, stackaddr,
atrsize)) != 0) {
perror ("PTHREAD_ATTR_GETSTACK");
return (errno);
}
printf ("Адрес начала стека: %p\n", stackaddr);
printf ("Размер стека: %d\n", atrsize);
assert (pthread_attr_getguardsize (patob, atrsize) == 0);
printf ("Размер защитной области: %d\n", atrsize);
assert (pthread_attr_getschedparam (patob, shdprm) == 0);
assert (pthread_attr_getschedpolicy (patob, res) == 0);
switch (res) {
case SCHED_FIFO:
spname = "Планирование по очереди";
break;
case SCHED_RR:
spname = "Циклическое планирование";
break;
case SCHED_OTHER:
spname = "Прочее планирование";
break;
default:
spname = "Неизвестная политика планирования";
}
printf ("Политика планирования: %s\n", spname);
printf ("Приоритет планирования: %d\n",
shdprm.sched_priority);
assert (pthread_attr_getscope (patob, res) == 0);
switch (res) {
case PTHREAD_SCOPE_SYSTEM:
spname = "Система";
break;
case PTHREAD_SCOPE_PROCESS:
spname = "Процесс";
break;
default:
spname = "Неизвестная область планирования "
"конкуренции";
}
printf ("Область планирования конкуренции: %s\n", spname);
assert (pthread_attr_getinheritsched (patob, res) == 0);
switch (res) {
case PTHREAD_INHERIT_SCHED:
spname = "Наследуются у родительского потока";
break;
case PTHREAD_EXPLICIT_SCHED:
spname = "Извлекаются из атрибутного объекта";
break;
default:
spname = "Устанавливаются неизвестным образом";
}
printf ("Атрибуты планирования: %s\n", spname);
assert (pthread_attr_getdetachstate (patob, res) == 0);
switch (res) {
case PTHREAD_CREATE_JOINABLE:
spname = "Присоединяемые";
break;
case PTHREAD_CREATE_DETACHED:
spname = "Обособленные";
break;
default:
spname = "Неизвестные";
}
printf ("Потоки управления создаются как: %s\n", spname);
/* Изменим значения атрибутов планирования и уровня */
/* параллелизма */
shdprm.sched_priority = 1;
if ((errno = pthread_setschedparam (ct_id, SCHED_RR,
shdprm)) != 0) {
perror ("PTHREAD_SETSCHEDPARAM");
}
if ((errno = pthread_setconcurrency (8192)) != 0) {
perror ("PTHREAD_SETCONCURRENCY");
}
printf ("\nТекущие значения атрибутов потоков управления\n");
assert (pthread_getschedparam (ct_id, res, shdprm) == 0);
switch (res) {
case SCHED_FIFO:
spname = "Планирование по очереди";
break;
case SCHED_RR:
spname = "Циклическое планирование";
break;
case SCHED_OTHER:
spname = "Прочее планирование";
break;
default:
spname = "Неизвестная политика планирования";
}
printf ("Политика планирования: %s\n", spname);
printf ("Приоритет планирования: %d\n",
shdprm.sched_priority);
printf ("Уровень параллелизма: %d\n",
pthread_getconcurrency());
return 0;
}
Идентификатор текущего потока управления: 400
Значения, установленные системой в атрибутном объекте
Адрес начала стека: 0xffe01000
Размер стека: 2093056
Размер защитной области: 4096
Политика планирования: Прочее планирование
Приоритет планирования: 0
Область планирования конкуренции: Система
Атрибуты планирования: Извлекаются из атрибутного объекта
Потоки управления создаются как: Присоединяемые
Текущие значения атрибутов потоков управления
Политика планирования: Циклическое планирование
Приоритет планирования: 1
Уровень параллелизма: 8192
Листинг 1.18 содержит результаты выполнения упрощенного варианта этой же программы (без вызовов функций pthread_attr_getstack(), pthread_attr_getguardsize(), pthread_getconcurrency(), pthread_setconcurrency() и без соответствующих выдач) для операционной системы реального времени oc2000, соответствующей подмножеству требований стандарта POSIX-2001.
Идентификатор текущего потока управления: f31ae0
Значения, установленные системой в атрибутном объекте
Политика планирования: Планирование по очереди
Приоритет планирования: 100
Область планирования конкуренции: Процесс
Атрибуты планирования: Извлекаются из атрибутного объекта
Потоки управления создаются как: Присоединяемые
Текущие значения атрибутов потоков управления
Политика планирования: Циклическое планирование
Приоритет планирования: 1
pthread_key_create() создать pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому)
Создать один
Для решения проблемы однократного выполнения 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));
При первом и только при первом обращении к функции pthread_once() с фиксированным значением аргумента once_control_ptr, вне зависимости от того, какой из (*init_routine) (), которая по идее должна осуществлять
Переменная, на которую указывает аргумент once_control_ptr, должна иметь начальное значение PTHREAD_ONCE_INIT и не должна быть автоматической.
За создание и удаление 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);
Функция 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);
Функция 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.23.
Время начала операций потока управления: 1075707670 сек, 584737 мсек
Для pthread_create() (см. листинг 1.24).
#include <pthread.h>
int pthread_create (
pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine) (void *),
void *restrict arg);
Выполнение созданного (*start_routine) (arg); возврат из этой функции приведет к main(), возврат из нее означает завершение
Аргумент attr задает attr равно NULL, используются зависящие от реализации подразумеваемые
От "родительского" вновь созданный
К числу средств fork(). Правда, здесь нас будет интересовать не она сама, а ассоциированные с ней обработчики, зарегистрированные с помощью функции pthread_atfork() (см. листинг 1.25).
#include <pthread.h>
int pthread_atfork (
void (*prepare) (void),
void (*parent) (void),
void (*child) (void));
В каждом обращении к pthread_atfork() фигурируют три обработчика (если, конечно, в качестве значения аргумента не задан пустой указатель). Первый ( (*prepare)() ) выполняется в контексте fork(), до разветвления (*parent)() ) – в том же контексте, но после разветвления; третий ( (*child)() ) – в контексте единственного
С помощью pthread_atfork() можно зарегистрировать несколько троек обработчиков. Первые элементы троек вызываются в порядке, обратном по отношению к регистрации; вторые и третьи выполняются в прямом порядке.
Как и pthread_exit() (см. листинг 1.26).
#include <pthread.h> void pthread_exit (void *value_ptr);
Из общих соображений (например, если исходить из аналогии между pthread_join() (см. листинг 1.27), напоминающей waitpid().
#include <pthread.h>
int pthread_join (
pthread_t thread, void **value_ptr_ptr);
pthread_join(), приостанавливает выполнение до завершения thread. При успешном возврате из pthread_join() результат, как и положено, равен нулю, а по указателю value_ptr_ptr (если он не пуст) помещается значение (указатель value_ptr ), переданное в качестве аргумента функции pthread_exit(). Тем самым ждуший
Отметим, что трактовка значения value_ptr возлагается на приложение. Например, оно может считать его целым числом, а не указателем; по этой причине операционная система при выполнении функции pthread_exit() не вправе выдавать ошибку типа "неверный адрес" каким бы ни был аргумент value_ptr. Если он все же является указателем, то ему нельзя присваивать адрес
Второе общее соображение касается того обстоятельства, что вызов такой функции, как pthread_join() ) способен приостановить выполнение вызывающего read() ) завершается с частично достигнутым результатом (например, с числом прочитанных байт, меньшим запрошенного) и кодом ошибки EINTR, нуждающимся в нестандартной обработке, далеко не всегда реализуемой разработчиками приложений. Из-за этого в программах появляются дефекты, которые трудно воспроизвести и, соответственно, исправить.
Согласно стандарту POSIX-2001, функции, обслуживающие EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.
Третье общее соображение состоит в том, что такое критически важное событие, как завершение pthread_cleanup_push() и pthread_cleanup_pop() (см. листинг 1.28).
#include <pthread.h>
void pthread_cleanup_push (
void (*routine) (void *), void *arg);
void pthread_cleanup_pop (int execute);
Функция pthread_cleanup_push() помещает заданный аргументами routine и arg обработчик в стек обработчиков вызывающего pthread_cleanup_pop() извлекает верхний обработчик из этого стека и, если значение аргумента execute отлично от нуля, вызывает его (как (*routine) (arg) ).
Разумеется, все обработчики, начиная с верхнего, извлекаются из стека и вызываются при pthread_exit().
Напомним, что atexit() ), однако применительно к
pthread_cleanup_push() и pthread_cleanup_pop() можно представлять себе как открывающую и закрывающую скобки, оформленные в виде отдельных инструкций языка C и обрамляющие обслуживаемый обработчиком участок программы. Согласно стандарту POSIX-2001, этот участок должен представлять собой фрагмент одной лексической области видимости (блока), а pthread_cleanup_push() и pthread_cleanup_pop() могут быть реализованы как макросы (см. листинг 1.29).
#define pthread_cleanup_push (rtn, arg) { \
struct _pthread_handler_rec \
__cleanup_handler, \
**__head; \
__cleanup_handler.rtn = rtn; \
__cleanup_handler.arg = arg; \
(void) pthread_getspecific \
(_pthread_handler_key, __head); \
__cleanup_handler.next = *__head; \
*__head = __cleanup_handler;
#define pthread_cleanup_pop (ex) \
*__head = __cleanup_handler.next; \
if (ex) (*__cleanup_handler.rtn) \
(__cleanup_handler.arg); \
}
Обратим внимание на то, что в определении макроса pthread_cleanup_push() открывается внутренний блок, в котором декларируются два необходимых объекта – структура __cleanup_handler, описывающая обработчик, и указатель __head на вершину стека, представленного в виде односвязанного (линейного) списка. В определении pthread_cleanup_pop() этот блок закрывается. Так что даже из соображений pthread_cleanup_push() и pthread_cleanup_pop() должны быть парными и располагаться в одном блоке, но более существенной нам представляется корректность семантическая.
Если
После того, как выполнятся все
Отметим, что если atexit() ).
Заказать pthread_cancel() (см. листинг 1.30).
#include <pthread.h> int pthread_cancel (pthread_t thread);
Напомним, что на выполнение "заказа" влияют состояние pthread_setcancelstate(), pthread_setcanceltype() и pthread_testcancel() (см. листинг 1.31).
#include <pthread.h>
int pthread_setcancelstate (
int state, int *oldstate);
int pthread_setcanceltype (
int type, int *oldtype);
void pthread_testcancel (void);
Функции pthread_setcancelstate() и pthread_setcanceltype() атомарным образом, в рамках неделимой транзакции устанавливают новые значения ( state и type ) для состояния oldstate и oldtype ) старые значения. Допустимыми значениями для состояния PTHREAD_CANCEL_ENABLE ( PTHREAD_CANCEL_DISABLE, для типа – PTHREAD_CANCEL_DEFERRED ( PTHREAD_CANCEL_ASYNCHRONOUS (немедленное,
Функция pthread_testcancel() создает в вызывающем
Из общих соображений следует, что манипуляции с
Возвращаясь к функции pthread_cancel(), отметим, что иногда обработку заказа на
Противоречат отмеченной аналогии следующие обстоятельства. Во-первых, обработка сигналов зачастую направлена на продолжение, а не завершение выполнения и, следовательно, носит принципиально иной, чем у siglongjmp() ), являющиеся стандартным элементом обработки сигналов, применительно к
Разумеется, функция pthread_cancel() не ожидает выполнения заказа на PTHREAD_CANCELED.
Помимо заказа на pthread_kill() (см. листинг 1.32).
#include <signal.h>
int pthread_kill (
pthread_t thread, int sig);
Как и в случае функции kill(), при нулевом значении аргумента проверяется корректность заданного
Напомним (см. курс [1]), что сигналы генерируются для конкретного pthread_kill() ) или для kill() ), но доставляются они всегда одному
Для иллюстрации изложенного приведем небольшую программу (см. листинг 1.33), в которой создается SIGINT. Возможные результаты работы этой программы показаны на листинге 1.34.
/* * * * * * * * * * * * * * * * * */
/* Программа демонстрирует генерацию */
/* и доставку сигналов */
/* потокам управления */
/* * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
/* * * * * * * * * * * * * * /
/* Функция обработки сигнала */
/* * * * * * * * * * * * * * /
static void signal_handler (int dummy) {
printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n",
pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока управления, */
/* которому будет направлен сигнал */
/* * * * * * * * * * * * * * * * * * * */
static void *thread_start (void *dummy) {
printf ("Идентификатор нового потока управления: %lx\n",
pthread_self ());
while (1) {
sleep (1);
}
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGINT, */
/* создает поток управления и посылает ему сигнал */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
pthread_t thread_id;
struct sigaction act;
/* Установим реакцию на сигнал SIGINT */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act,
(struct sigaction *) NULL);
if ((errno = pthread_create (thread_id, NULL,
thread_start, NULL)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
printf ("Идентификатор созданного потока управления: %lx\n",
thread_id);
(void) pthread_kill (thread_id, SIGINT);
printf ("После вызова pthread_kill()\n");
sleep (1);
printf ("Выспались...\n");
return (0);
}
Идентификатор созданного потока управления: 402
После вызова pthread_kill()
Идентификатор потока, обрабатывающего сигнал: 402
Идентификатор нового потока управления: 402
Выспались...
Обратим внимание на два любопытных (хотя и довольно очевидных) момента. Во-первых, начальный pthread_kill() и успевает сообщить об этом. Затем он на секунду засыпает,
Читателю предлагается самостоятельно проанализировать, как будет вести себя приведенная программа при подразумеваемом способе обработки сигнала SIGINT.
Отмеченную выше устойчивость
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа демонстрирует взаимодействие сигналов */
/* и ожидания завершения потока управления */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
/* * * * * * * * * * * * * * */
/* Функция обработки сигнала */
/* * * * * * * * * * * * * * */
static void signal_handler (int dummy) {
printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n",
pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция создаваемого потока управления */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void *thread_start (void *thread_id) {
printf ("Идентификатор нового потока управления: %lx\n",
pthread_self ());
(void) pthread_kill ((pthread_t) thread_id, SIGINT);
return ((void *) pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGINT, */
/* создает поток управления и ожидает его завершения */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
pthread_t thread_id;
struct sigaction act;
void *pv;
/* Установим реакцию на сигнал SIGINT */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act, (struct sigaction *) NULL);
if ((errno = pthread_create (thread_id, NULL,
thread_start, (void *) pthread_self ())) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
printf ("Идентификаторы начального и созданного потоков "
"управления: " "%lx %lx\n", pthread_self (),
thread_id);
/* Дождемся завершения созданного потока управления */
if ((errno = pthread_join (thread_id, pv)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
printf ("Статус завершения созданного потока "
"управления: %p\n", pv);
return (0);
}
Если посмотреть на возможные результаты работы этой программы (см. листинг 1.36), можно сделать вывод, что, несмотря на получение и обработку сигнала, функция pthread_join отрабатывает с нормальным (нулевым) результатом, получая
Идентификаторы начального и созданного потоков управления: 400 402 Идентификатор нового потока управления: 402 Идентификатор потока, обрабатывающего сигнал: 400 Статус завершения созданного потока управления: 0x402
И здесь читателю рекомендуется самостоятельно выяснить, как поведет себя приведенная программа при подразумеваемой реакции на сигнал SIGINT.
Еще одна полезная операция, связанная с обработкой завершения pthread_detach() (см. листинг 1.37).
#include <pthread.h> int pthread_detach (pthread_t thread);
При завершении
Может показаться, что возможность pthread_create() ), однако это не так. Во-первых, начальный pthread_join(),
Если приложение заботится об аккуратном освобождении памяти, то для всех PTHREAD_CREATE_JOINABLE, следует предусмотреть вызов либо pthread_join(), либо pthread_detach().
В качестве примера многопотоковой программы приведем серверную часть рассматривавшегося в курсе [1] приложения, копирующего строки со стандартного ввода на стандартный вывод с "прокачиванием" их через потоковые сокеты (см. листинг 1.38).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его серверным), */
/* принимающего запросы на установления соединения и */
/* запускающего потоки управления для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая (и единственная) функция потоков управления, */
/* обслуживающих запросы на копирование строк, */
/* поступающих из сокета */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *srv_thread_start (void *ad) {
FILE *fpad; /* Поток данных, соответствующий */
/* дескриптору ad */
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Структура для записи адреса */
struct sockaddr_in sai;
/* Длина адреса */
socklen_t sai_len = sizeof (struct sockaddr_in);
/* Опросим адрес партнера по общению (передающего сокета) */
if (getpeername ((int) ad, (struct sockaddr *) sai,
sai_len) < 0) {
perror ("GETPEERNAME");
return (NULL);
}
/* По файловому дескриптору ad сформируем */
/* буферизованный поток данных */
if ((fpad = fdopen ((int) ad, "r")) == NULL) {
perror ("FDOPEN");
return (NULL);
}
/* Цикл чтения строк из сокета */
/* и выдачи их на стандартный вывод */
while (fgets (line, sizeof (line), fpad) != NULL) {
printf ("Вы ввели и отправили с адреса %s, "
"порт %d :", inet_ntoa (sai.sin_addr),
ntohs (sai.sin_port));
fputs (line, stdout);
}
/* Закрытие соединения */
shutdown ((int) ad, SHUT_RD);
(void) fclose (fpad);
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* В функции main() принимаются запросы на установление */
/* соединения и запускаются потоки управления для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Буфер для принимаемых строк */
struct addrinfo hints = {AI_PASSIVE, AF_INET,
SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};
/* Указатель – выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
pthread_attr_t patob; /* Атрибутный объект для создания */
/* потоков управления */
pthread_t adt_id; /* Идентификатор обслуживающего потока управления */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM,
IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler */
/* на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints,
addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n",
gai_strerror (res));
return (2);
}
if (bind (sd, addr_res->ai_addr,
addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (3);
}
/* Можно освободить память, которую запрашивала */
/* функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (4);
}
/* Инициализируем атрибутный объект потоков управления */
if ((errno = pthread_attr_init (patob)) != 0) {
perror ("PTHREAD_ATTR_INIT");
return (errno);
}
/* Потоки управления будем создавать обособленными */
(void) pthread_attr_setdetachstate (patob,
PTHREAD_CREATE_DETACHED);
/* Цикл приема соединений и запуска */
/* обслуживающих потоков управления */
while (1) {
/* Примем соединение. */
/* Адрес партнера по общению нас */
/* в данном случае не интересует */
if ((ad = accept (sd, NULL, NULL)) < 0) {
perror ("ACCEPT");
return (6);
}
/* Запустим обслуживающий поток управления */
if ((errno = pthread_create (adt_id, patob,
srv_thread_start,(void *) ad)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
}
return (0);
}
Многопотоковая реализация в данном случае уместнее многопроцессной: она и выглядит проще (поскольку
Можно надеяться, что и следующая программа (см. листинг 1.39), реализующая идею обработки данных с контролем времени, подтверждает, что применение
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа вызывает функции обработки в рамках */
/* порождаемых потоков управления и контролирует время */
/* их выполнения с помощью интервального таймера */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
/* Период интервального таймера (в секундах) */
#define IT_PERIOD 1
static pthread_t cthread_id; /* Идентификатор текущего */
/* потока управления, */
/* обрабатывающего данные */
static int in_proc_data = 0; /* Признак активности */
/* потока обработки данных */
static double s; /* Результат функций */
/* обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция обработки срабатывания таймера реального */
/* времени (сигнал SIGALRM) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_sigalrm (int dummy) {
if (in_proc_data) {
/* Не имеет значения, какой поток обрабатывает сигнал */
/* и заказывает терминирование (быть может, себя) */
(void) pthread_cancel (cthread_id);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Обработчик завершения потока управления. */
/* Сбрасывает признак активности потока обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_cleanup_handler (void *arg) {
in_proc_data = (int) arg;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока управления, обрабатывающего */
/* данные. Аргумент – указатель на функцию обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_func (void *proc_data_func) {
/* Поместим в стек обработчик завершения */
pthread_cleanup_push (proc_data_cleanup_handler, 0);
in_proc_data = 1; /* Время пошло ... */
/* На время выполнения функции обработки данных установим */
/* асинхронный тип терминирования, иначе оно не сработает */
(void) pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS,
NULL);
/* Выполним функцию обработки данных */
((void (*) (void)) (proc_data_func)) ();
/* Установим отложенный тип терминирования, */
/* иначе изъятие обработчика из стека */
/* будет небезопасным действием */
(void) pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED,
NULL);
/* Выполним обработчик завершения и удалим его из стека */
pthread_cleanup_pop (1);
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Первая функция обработки данных (вычисляет ln (2)) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_1 (void) {
double d = 1;
int i;
s = 0;
for (i = 1; i <= 100000000; i++) {
s += d / i;
d = -d;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Вторая функция обработки данных (вычисляет sqrt (2))*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_2 (void) {
s = 1;
do {
s = (s + 2 / s) * 0.5;
} while ((s * s – 2) > 0.000000001);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGALRM, */
/* взводит периодический таймер реального времени */
/* и запускает в цикле потоки обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
/* Массив указателей на функции обработки данных */
void (*fptrs []) (void) = {proc_data_1,
proc_data_2, NULL};
/* Указатель на указатель на */
/* текущую функцию обработки данных */
void (**tfptr) (void);
void *pstat; /* Статус завершения потока */
/* обработки данных */
struct itimerval itvl;
struct sigaction sact;
int i;
/* Установим реакцию на сигнал SIGALRM */
sact.sa_handler = proc_sigalrm;
sact.sa_flags = 0;
(void) sigemptyset (sact.sa_mask);
if (sigaction (SIGALRM, sact, NULL) < 0) {
perror ("SIGACTION");
return (1);
}
/* Сделаем таймер реального времени периодическим */
itvl.it_interval.tv_sec = IT_PERIOD;
itvl.it_interval.tv_usec = 0;
/* Цикл запуска потоков обработки данных. */
/* Выполним его дважды */
for (i = 0; i < 2; i++) {
for (tfptr = fptrs; *tfptr != NULL; tfptr++) {
/* Взведем интервальный таймер реального времени */
itvl.it_value.tv_sec = IT_PERIOD;
itvl.it_value.tv_usec = 0;
if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
perror ("SETITIMER");
return (2);
}
/* Создадим поток обработки данных, */
/* затем дождемся его завершения */
if ((errno = pthread_create (cthread_id, NULL,
start_func, (void *) *tfptr)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
if ((errno = pthread_join (cthread_id,
pstat)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
if (pstat == PTHREAD_CANCELED) {
printf ("Частичный результат функции "
"обработки данных: %g\n", s);
} else {
printf ("Полный результат функции "
"обработки данных: %g\n", s);
}
}
}
return 0;
}
Возможные результаты работы приведенной программы показаны на листинге 1.40.
Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421 Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421
К сожалению, там, где есть недетерминированность и асинхронность, без тонкостей все равно не обойтись. Мы обратим внимание на три из них. Во-первых, возможно SIGALRM до того, как завершится (или даже начнется) первый вызов pthread_create() и будет инициализирована переменная cthread_id. Чтобы не допустить cthread_id заведомо инициализирована.
Во-вторых, не имеет значения, в контексте какого из
В-третьих, поскольку функции обработки данных не содержат точек
Как мы уже упоминали,
На листингах 1.41 и 1.42 показана программа, которая в цикле порождает практически пустые time -p. Даже если сделать , времена получатся довольно большими (см. листинг 1.44) в сравнении с аналогичными данными для варианта с
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define N 10000
int main (void) {
int i;
for (i = 0; i < N; i++) {
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
/* Порожденный процесс */
(void) execl ("./dummy", "dummy",
(char *) 0);
exit (0);
default:
/* Родительский процесс */
(void) wait (NULL);
}
}
return 0;
}
int main (void) {
return 0;
}
real 34.97 user 12.36 sys 22.61
real 11.49 user 2.38 sys 9.11
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define N 10000
static void *thread_start (void *arg) {
pthread_exit (arg);
}
int main (void) {
pthread_t thread_id;
int i;
for (i = 0; i < N; i++) {
if ((errno = pthread_create (
thread_id, NULL,
thread_start, NULL)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
if ((errno = pthread_join (
thread_id, NULL)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
}
return (0);
}
real 2.08 user 0.52 sys 1.56
В первом приближении можно считать, что
Можно сделать вывод, что
Напомним, уточним и дополним определения, которые были даны в курсе [1] применительно к
После того, как fork(), он считается fork().
До завершения
Большинство errno, ассоциированные с
pthread_t, который трактуется в стандарте POSIX-2001 как абстрактный. В частности, для него определен метод сравнения значений на равенство.
Всем malloc(), прямоадресуемая память, полученная от системно-зависимых функций,
PTHREAD_SCOPE_SYSTEM ( PTHREAD_SCOPE_PROCESS (
SIGSEGV
pthread_attr_t, внутренняя структура значений которого скрыта от приложений. Смысл введения
Обратим внимание на следующее обстоятельство, важное для реализации многопотоковых приложений. Иногда
Операции с
В таком порядке они и будут рассматриваться далее. Отметим, что стандарт POSIX-2001 относит их к необязательной части, именуемой, как нетрудно догадаться, "
fork() / exec() создающий pthread_create() posix_spawn() ) для порождения
Согласно стандарту POSIX-2001, accept(), connect(), msgrcv(), msgsnd(), pause(), read(), sleep(), wait(), write() и сходных с ними по поведению. Допускается существование подобных точек и в других, также оговоренных в стандарте POSIX функциях – printf(), scanf(), semop() и т.п.
Следуя классическому принципу "познай самого себя", описание функций, обслуживающих pthread_self(), возвращающей в качестве результата
#include <pthread.h> pthread_t pthread_self (void);
Выше мы отмечали, что тип pthread_t трактуется стандартом POSIX-2001 как абстрактный. На уровне языка C он может быть представлен, например, структурой. Для работы со значениями типа pthread_t предусмотрены два метода: присваивание и сравнение на равенство, реализуемое функцией pthread_equal() (см. листинг 1.2).
#include <pthread.h>
int pthread_equal (pthread_t t1,
pthread_t t2);
Если значения аргументов t1 и t2 равны, результат функции pthread_equal() отличен от нуля.
pthread_attr_init() и pthread_attr_destroy() (см. листинг 1.3).
#include <pthread.h>
int pthread_attr_init (
pthread_attr_t *attr);
int pthread_attr_destroy (
pthread_attr_t *attr);
Функция pthread_attr_init() инициализирует attr, подразумеваемыми значениями для всех индивидуальных
Функция pthread_attr_destroy() разрушает заданный
Структура
#include <pthread.h>
int pthread_attr_getstack (
const pthread_attr_t *restrict attr,
void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack (
pthread_attr_t *attr, void *stackaddr,
size_t stacksize);
Размер стека должен составлять не менее PTHREAD_STACK_MIN, начальный адрес – должным образом выровнен. Память, отведенная под стек, должна быть доступна на чтение и запись.
Функция pthread_attr_getstack() помещает атрибуты стека по указателям stackaddr и stacksize. Это – проявление единообразной для семейства функций pthread*(), обслуживающих
Подобная дисциплинированность является похвальной, но вынужденной. Ее причина – в разделении данных между malloc() ) и передать функции указатели на них.
Для опроса и изменения размера pthread_attr_getguardsize() и pthread_attr_setguardsize() (см. листинг 1.5).
#include <pthread.h>
int pthread_attr_getguardsize (
const pthread_attr_t *restrict attr,
size_t *restrict guardsize);
int pthread_attr_setguardsize (
pthread_attr_t *attr,
size_t guardsize);
Если значение аргумента guardsize функции pthread_attr_setguardsize() равно нулю, при *attr guardsize также становятся новыми значениями одноименного атрибута, однако являются лишь указанием операционной системе; реальный размер
Приложение, соответствующее стандарту POSIX, должно использовать значения guardsize, кратные конфигурационной константе PAGESIZE, которая одновременно является подразумеваемым значением данного атрибута.
Если приложение посредством функции pthread_attr_setstack() взяло на себя управление стеками guardsize игнорируется, операционная система не отводит
Отметим, что, в зависимости от ситуации, приложениям есть смысл как отказываться от
Стандартом POSIX-2001 предусмотрена группа <sched.h>. Центральную роль среди них играет структура типа sched_param, которая должна содержать по крайней мере поле int sched_priority; /* Приоритет планирования при выполнении потока */
SCHED_SPORADIC ( _POSIX_SPORADIC_SERVER и/или _POSIX_THREAD_SPORADIC_SERVER, а в структуре sched_param должны присутствовать следующие дополнительные поля
int sched_ss_low_priority;
/* Нижняя граница приоритета */
/* планирования сервера */
/* спорадических событий */
struct timespec sched_ss_repl_period;
/* Период пополнения бюджета */
/* спорадического сервера */
struct timespec sched_ss_init_budget;
/* Начальный бюджет */
/* спорадического сервера */
int sched_ss_max_repl;
/* Максимальное число */
/* ждущих операций */
/* пополнений бюджета */
/* спорадического сервера */
Для опроса и установки pthread_attr_getschedparam() и pthread_attr_setschedparam() (см. листинг 1.6).
#include <pthread.h>
int pthread_attr_getschedparam (
const pthread_attr_t *restrict attr,
struct sched_param *restrict param);
int pthread_attr_setschedparam (
pthread_attr_t *restrict attr,
const struct sched_param *restrict param);
SCHED_FIFO ( SCHED_RR ( SCHED_OTHER ( SCHED_SPORADIC ( pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy() (см. листинг 1.7).
#include <pthread.h>
int pthread_attr_getschedpolicy (
const pthread_attr_t *restrict attr,
int *restrict policy);
int pthread_attr_setschedpolicy (
pthread_attr_t *attr, int policy);
Описанный выше PTHREAD_SCOPE_SYSTEM и PTHREAD_SCOPE_PROCESS, обслуживают функции pthread_attr_getscope() и pthread_attr_setscope() (см. листинг 1.8).
#include <pthread.h>
int pthread_attr_getscope (
const pthread_attr_t *restrict attr,
int *restrict contentionscope);
int pthread_attr_setscope (
pthread_attr_t *attr,
int contentionscope);
При PTHREAD_INHERIT_SCHED или PTHREAD_EXPLICIT_SCHED inheritsched, могут pthread_attr_getinheritsched() и pthread_attr_setinheritsched() (см. листинг 1.9).
#include <pthread.h>
int pthread_attr_getinheritsched (
const pthread_attr_t *restrict attr,
int *restrict inheritsched);
int pthread_attr_setinheritsched (
pthread_attr_t *attr,
int inheritsched);
pthread_attr_getdetachstate() и pthread_attr_setdetachstate() (см. листинг 1.10).
#include <pthread.h>
int pthread_attr_getdetachstate (
const pthread_attr_t *attr,
int *detachstate);
int pthread_attr_setdetachstate (
pthread_attr_t *attr,
int detachstate);
Напомним, что значение этого PTHREAD_CREATE_DETACHED или PTHREAD_CREATE_JOINABLE ) определяет, будет ли PTHREAD_CREATE_JOINABLE.
Значения
#include <pthread.h>
int pthread_getschedparam (
pthread_t thread, int *restrict policy,
struct sched_param *restrict param);
int pthread_setschedparam (
pthread_t thread, int policy,
const struct sched_param *param);
Отметим две тонкости, связанные с функцией pthread_setschedparam(). Во-первых, возможно, что для ее успешного вызова SCHED_SPORADIC ) (как, впрочем, и саму эту
Если требуется изменить лишь pthread_setschedprio() (см. листинг 1.12), которая, правда, является новой и в исторически сложившихся реализациях может отсутствовать.
#include <pthread.h>
int pthread_setschedprio (
pthread_t thread, int prio);
Сходную направленность, но более глобальный характер имеют функции pthread_getconcurrency() и pthread_setconcurrency() (см. листинг 1.13), позволяющие опросить и изменить
#include <pthread.h> int pthread_getconcurrency (void); int pthread_setconcurrency (int new_level);
По умолчанию операционная система предоставляет возможность параллельно проявлять активность некоему "достаточному числу" new_level функции pthread_setconcurrency(). Впрочем, с точки зрения операционной системы это всего лишь просьба или рекомендация; стандарт не специфицирует реально устанавливаемый уровень.
Нулевое значение аргумента new_level означает переход к подразумеваемому pthread_setconcurrency() ранее не вызывалась.
Функция pthread_getconcurrency() в качестве результата возвращает значение pthread_setconcurrency(). Если такового не было, выдается нуль.
Отметим, что изменение
К числу pthread_getcpuclockid() (см. листинг 1.14).
#include <pthread.h>
#include <time.h>
int pthread_getcpuclockid (
pthread_t thread_id,
clockid_t *clock_id);
Еще один pthread_sigmask() (см. листинг 1.15) – аналога рассмотренной в курсе [1] функции sigprocmask().
#include <signal.h>
int pthread_sigmask (
int how, const sigset_t *restrict set,
sigset_t *restrict oset);
На листинге 1.16 приведен пример программы, использующей большинство описанных выше функций для опроса и изменения
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа опрашивает атрибуты потоков управления */
/* и изменяет некоторые из них */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <assert.h>
int main (void) {
pthread_t ct_id; /* Идентификатор текущего потока управления */
pthread_attr_t patob; /* Атрибутный объект для создания потоков управления */
int res; /* Переменная для запоминания результатов "потоковых" функций */
void *stackaddr; /* Начало стека как атрибут потока управления */
size_t atrsize; /* Размеры как атрибуты потока управления */
/* Структура с параметрами планирования */
struct sched_param shdprm;
char *spname; /* Названия политики планирования, области */
/* планирования конкуренции и т.п. */
printf ("Идентификатор текущего потока управления: %lx\n",
(ct_id = pthread_self ()));
if ((errno = pthread_attr_init (patob)) != 0) {
perror ("PTHREAD_ATTR_INIT");
return (errno);
}
printf ("Значения, установленные системой "
"в атрибутном объекте\n");
if ((errno = pthread_attr_getstack (patob, stackaddr,
atrsize)) != 0) {
perror ("PTHREAD_ATTR_GETSTACK");
return (errno);
}
printf ("Адрес начала стека: %p\n", stackaddr);
printf ("Размер стека: %d\n", atrsize);
assert (pthread_attr_getguardsize (patob, atrsize) == 0);
printf ("Размер защитной области: %d\n", atrsize);
assert (pthread_attr_getschedparam (patob, shdprm) == 0);
assert (pthread_attr_getschedpolicy (patob, res) == 0);
switch (res) {
case SCHED_FIFO:
spname = "Планирование по очереди";
break;
case SCHED_RR:
spname = "Циклическое планирование";
break;
case SCHED_OTHER:
spname = "Прочее планирование";
break;
default:
spname = "Неизвестная политика планирования";
}
printf ("Политика планирования: %s\n", spname);
printf ("Приоритет планирования: %d\n",
shdprm.sched_priority);
assert (pthread_attr_getscope (patob, res) == 0);
switch (res) {
case PTHREAD_SCOPE_SYSTEM:
spname = "Система";
break;
case PTHREAD_SCOPE_PROCESS:
spname = "Процесс";
break;
default:
spname = "Неизвестная область планирования "
"конкуренции";
}
printf ("Область планирования конкуренции: %s\n", spname);
assert (pthread_attr_getinheritsched (patob, res) == 0);
switch (res) {
case PTHREAD_INHERIT_SCHED:
spname = "Наследуются у родительского потока";
break;
case PTHREAD_EXPLICIT_SCHED:
spname = "Извлекаются из атрибутного объекта";
break;
default:
spname = "Устанавливаются неизвестным образом";
}
printf ("Атрибуты планирования: %s\n", spname);
assert (pthread_attr_getdetachstate (patob, res) == 0);
switch (res) {
case PTHREAD_CREATE_JOINABLE:
spname = "Присоединяемые";
break;
case PTHREAD_CREATE_DETACHED:
spname = "Обособленные";
break;
default:
spname = "Неизвестные";
}
printf ("Потоки управления создаются как: %s\n", spname);
/* Изменим значения атрибутов планирования и уровня */
/* параллелизма */
shdprm.sched_priority = 1;
if ((errno = pthread_setschedparam (ct_id, SCHED_RR,
shdprm)) != 0) {
perror ("PTHREAD_SETSCHEDPARAM");
}
if ((errno = pthread_setconcurrency (8192)) != 0) {
perror ("PTHREAD_SETCONCURRENCY");
}
printf ("\nТекущие значения атрибутов потоков управления\n");
assert (pthread_getschedparam (ct_id, res, shdprm) == 0);
switch (res) {
case SCHED_FIFO:
spname = "Планирование по очереди";
break;
case SCHED_RR:
spname = "Циклическое планирование";
break;
case SCHED_OTHER:
spname = "Прочее планирование";
break;
default:
spname = "Неизвестная политика планирования";
}
printf ("Политика планирования: %s\n", spname);
printf ("Приоритет планирования: %d\n",
shdprm.sched_priority);
printf ("Уровень параллелизма: %d\n",
pthread_getconcurrency());
return 0;
}
Идентификатор текущего потока управления: 400
Значения, установленные системой в атрибутном объекте
Адрес начала стека: 0xffe01000
Размер стека: 2093056
Размер защитной области: 4096
Политика планирования: Прочее планирование
Приоритет планирования: 0
Область планирования конкуренции: Система
Атрибуты планирования: Извлекаются из атрибутного объекта
Потоки управления создаются как: Присоединяемые
Текущие значения атрибутов потоков управления
Политика планирования: Циклическое планирование
Приоритет планирования: 1
Уровень параллелизма: 8192
Листинг 1.18 содержит результаты выполнения упрощенного варианта этой же программы (без вызовов функций pthread_attr_getstack(), pthread_attr_getguardsize(), pthread_getconcurrency(), pthread_setconcurrency() и без соответствующих выдач) для операционной системы реального времени oc2000, соответствующей подмножеству требований стандарта POSIX-2001.
Идентификатор текущего потока управления: f31ae0
Значения, установленные системой в атрибутном объекте
Политика планирования: Планирование по очереди
Приоритет планирования: 100
Область планирования конкуренции: Процесс
Атрибуты планирования: Извлекаются из атрибутного объекта
Потоки управления создаются как: Присоединяемые
Текущие значения атрибутов потоков управления
Политика планирования: Циклическое планирование
Приоритет планирования: 1
pthread_key_create() создать pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому)
Создать один
Для решения проблемы однократного выполнения 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));
При первом и только при первом обращении к функции pthread_once() с фиксированным значением аргумента once_control_ptr, вне зависимости от того, какой из (*init_routine) (), которая по идее должна осуществлять
Переменная, на которую указывает аргумент once_control_ptr, должна иметь начальное значение PTHREAD_ONCE_INIT и не должна быть автоматической.
За создание и удаление 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);
Функция 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);
Функция 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.23.
Время начала операций потока управления: 1075707670 сек, 584737 мсек
Для pthread_create() (см. листинг 1.24).
#include <pthread.h>
int pthread_create (
pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine) (void *),
void *restrict arg);
Выполнение созданного (*start_routine) (arg); возврат из этой функции приведет к main(), возврат из нее означает завершение
Аргумент attr задает attr равно NULL, используются зависящие от реализации подразумеваемые
От "родительского" вновь созданный
К числу средств fork(). Правда, здесь нас будет интересовать не она сама, а ассоциированные с ней обработчики, зарегистрированные с помощью функции pthread_atfork() (см. листинг 1.25).
#include <pthread.h>
int pthread_atfork (
void (*prepare) (void),
void (*parent) (void),
void (*child) (void));
В каждом обращении к pthread_atfork() фигурируют три обработчика (если, конечно, в качестве значения аргумента не задан пустой указатель). Первый ( (*prepare)() ) выполняется в контексте fork(), до разветвления (*parent)() ) – в том же контексте, но после разветвления; третий ( (*child)() ) – в контексте единственного
С помощью pthread_atfork() можно зарегистрировать несколько троек обработчиков. Первые элементы троек вызываются в порядке, обратном по отношению к регистрации; вторые и третьи выполняются в прямом порядке.
Как и pthread_exit() (см. листинг 1.26).
#include <pthread.h> void pthread_exit (void *value_ptr);
Из общих соображений (например, если исходить из аналогии между pthread_join() (см. листинг 1.27), напоминающей waitpid().
#include <pthread.h>
int pthread_join (
pthread_t thread, void **value_ptr_ptr);
pthread_join(), приостанавливает выполнение до завершения thread. При успешном возврате из pthread_join() результат, как и положено, равен нулю, а по указателю value_ptr_ptr (если он не пуст) помещается значение (указатель value_ptr ), переданное в качестве аргумента функции pthread_exit(). Тем самым ждуший
Отметим, что трактовка значения value_ptr возлагается на приложение. Например, оно может считать его целым числом, а не указателем; по этой причине операционная система при выполнении функции pthread_exit() не вправе выдавать ошибку типа "неверный адрес" каким бы ни был аргумент value_ptr. Если он все же является указателем, то ему нельзя присваивать адрес
Второе общее соображение касается того обстоятельства, что вызов такой функции, как pthread_join() ) способен приостановить выполнение вызывающего read() ) завершается с частично достигнутым результатом (например, с числом прочитанных байт, меньшим запрошенного) и кодом ошибки EINTR, нуждающимся в нестандартной обработке, далеко не всегда реализуемой разработчиками приложений. Из-за этого в программах появляются дефекты, которые трудно воспроизвести и, соответственно, исправить.
Согласно стандарту POSIX-2001, функции, обслуживающие EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.
Третье общее соображение состоит в том, что такое критически важное событие, как завершение pthread_cleanup_push() и pthread_cleanup_pop() (см. листинг 1.28).
#include <pthread.h>
void pthread_cleanup_push (
void (*routine) (void *), void *arg);
void pthread_cleanup_pop (int execute);
Функция pthread_cleanup_push() помещает заданный аргументами routine и arg обработчик в стек обработчиков вызывающего pthread_cleanup_pop() извлекает верхний обработчик из этого стека и, если значение аргумента execute отлично от нуля, вызывает его (как (*routine) (arg) ).
Разумеется, все обработчики, начиная с верхнего, извлекаются из стека и вызываются при pthread_exit().
Напомним, что atexit() ), однако применительно к
pthread_cleanup_push() и pthread_cleanup_pop() можно представлять себе как открывающую и закрывающую скобки, оформленные в виде отдельных инструкций языка C и обрамляющие обслуживаемый обработчиком участок программы. Согласно стандарту POSIX-2001, этот участок должен представлять собой фрагмент одной лексической области видимости (блока), а pthread_cleanup_push() и pthread_cleanup_pop() могут быть реализованы как макросы (см. листинг 1.29).
#define pthread_cleanup_push (rtn, arg) { \
struct _pthread_handler_rec \
__cleanup_handler, \
**__head; \
__cleanup_handler.rtn = rtn; \
__cleanup_handler.arg = arg; \
(void) pthread_getspecific \
(_pthread_handler_key, __head); \
__cleanup_handler.next = *__head; \
*__head = __cleanup_handler;
#define pthread_cleanup_pop (ex) \
*__head = __cleanup_handler.next; \
if (ex) (*__cleanup_handler.rtn) \
(__cleanup_handler.arg); \
}
Обратим внимание на то, что в определении макроса pthread_cleanup_push() открывается внутренний блок, в котором декларируются два необходимых объекта – структура __cleanup_handler, описывающая обработчик, и указатель __head на вершину стека, представленного в виде односвязанного (линейного) списка. В определении pthread_cleanup_pop() этот блок закрывается. Так что даже из соображений pthread_cleanup_push() и pthread_cleanup_pop() должны быть парными и располагаться в одном блоке, но более существенной нам представляется корректность семантическая.
Если
После того, как выполнятся все
Отметим, что если atexit() ).
Заказать pthread_cancel() (см. листинг 1.30).
#include <pthread.h> int pthread_cancel (pthread_t thread);
Напомним, что на выполнение "заказа" влияют состояние pthread_setcancelstate(), pthread_setcanceltype() и pthread_testcancel() (см. листинг 1.31).
#include <pthread.h>
int pthread_setcancelstate (
int state, int *oldstate);
int pthread_setcanceltype (
int type, int *oldtype);
void pthread_testcancel (void);
Функции pthread_setcancelstate() и pthread_setcanceltype() атомарным образом, в рамках неделимой транзакции устанавливают новые значения ( state и type ) для состояния oldstate и oldtype ) старые значения. Допустимыми значениями для состояния PTHREAD_CANCEL_ENABLE ( PTHREAD_CANCEL_DISABLE, для типа – PTHREAD_CANCEL_DEFERRED ( PTHREAD_CANCEL_ASYNCHRONOUS (немедленное,
Функция pthread_testcancel() создает в вызывающем
Из общих соображений следует, что манипуляции с
Возвращаясь к функции pthread_cancel(), отметим, что иногда обработку заказа на
Противоречат отмеченной аналогии следующие обстоятельства. Во-первых, обработка сигналов зачастую направлена на продолжение, а не завершение выполнения и, следовательно, носит принципиально иной, чем у siglongjmp() ), являющиеся стандартным элементом обработки сигналов, применительно к
Разумеется, функция pthread_cancel() не ожидает выполнения заказа на PTHREAD_CANCELED.
Помимо заказа на pthread_kill() (см. листинг 1.32).
#include <signal.h>
int pthread_kill (
pthread_t thread, int sig);
Как и в случае функции kill(), при нулевом значении аргумента проверяется корректность заданного
Напомним (см. курс [1]), что сигналы генерируются для конкретного pthread_kill() ) или для kill() ), но доставляются они всегда одному
Для иллюстрации изложенного приведем небольшую программу (см. листинг 1.33), в которой создается SIGINT. Возможные результаты работы этой программы показаны на листинге 1.34.
/* * * * * * * * * * * * * * * * * */
/* Программа демонстрирует генерацию */
/* и доставку сигналов */
/* потокам управления */
/* * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
/* * * * * * * * * * * * * * /
/* Функция обработки сигнала */
/* * * * * * * * * * * * * * /
static void signal_handler (int dummy) {
printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n",
pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока управления, */
/* которому будет направлен сигнал */
/* * * * * * * * * * * * * * * * * * * */
static void *thread_start (void *dummy) {
printf ("Идентификатор нового потока управления: %lx\n",
pthread_self ());
while (1) {
sleep (1);
}
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGINT, */
/* создает поток управления и посылает ему сигнал */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
pthread_t thread_id;
struct sigaction act;
/* Установим реакцию на сигнал SIGINT */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act,
(struct sigaction *) NULL);
if ((errno = pthread_create (thread_id, NULL,
thread_start, NULL)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
printf ("Идентификатор созданного потока управления: %lx\n",
thread_id);
(void) pthread_kill (thread_id, SIGINT);
printf ("После вызова pthread_kill()\n");
sleep (1);
printf ("Выспались...\n");
return (0);
}
Идентификатор созданного потока управления: 402
После вызова pthread_kill()
Идентификатор потока, обрабатывающего сигнал: 402
Идентификатор нового потока управления: 402
Выспались...
Обратим внимание на два любопытных (хотя и довольно очевидных) момента. Во-первых, начальный pthread_kill() и успевает сообщить об этом. Затем он на секунду засыпает,
Читателю предлагается самостоятельно проанализировать, как будет вести себя приведенная программа при подразумеваемом способе обработки сигнала SIGINT.
Отмеченную выше устойчивость
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа демонстрирует взаимодействие сигналов */
/* и ожидания завершения потока управления */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
/* * * * * * * * * * * * * * */
/* Функция обработки сигнала */
/* * * * * * * * * * * * * * */
static void signal_handler (int dummy) {
printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n",
pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция создаваемого потока управления */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void *thread_start (void *thread_id) {
printf ("Идентификатор нового потока управления: %lx\n",
pthread_self ());
(void) pthread_kill ((pthread_t) thread_id, SIGINT);
return ((void *) pthread_self ());
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGINT, */
/* создает поток управления и ожидает его завершения */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
pthread_t thread_id;
struct sigaction act;
void *pv;
/* Установим реакцию на сигнал SIGINT */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act, (struct sigaction *) NULL);
if ((errno = pthread_create (thread_id, NULL,
thread_start, (void *) pthread_self ())) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
printf ("Идентификаторы начального и созданного потоков "
"управления: " "%lx %lx\n", pthread_self (),
thread_id);
/* Дождемся завершения созданного потока управления */
if ((errno = pthread_join (thread_id, pv)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
printf ("Статус завершения созданного потока "
"управления: %p\n", pv);
return (0);
}
Если посмотреть на возможные результаты работы этой программы (см. листинг 1.36), можно сделать вывод, что, несмотря на получение и обработку сигнала, функция pthread_join отрабатывает с нормальным (нулевым) результатом, получая
Идентификаторы начального и созданного потоков управления: 400 402 Идентификатор нового потока управления: 402 Идентификатор потока, обрабатывающего сигнал: 400 Статус завершения созданного потока управления: 0x402
И здесь читателю рекомендуется самостоятельно выяснить, как поведет себя приведенная программа при подразумеваемой реакции на сигнал SIGINT.
Еще одна полезная операция, связанная с обработкой завершения pthread_detach() (см. листинг 1.37).
#include <pthread.h> int pthread_detach (pthread_t thread);
При завершении
Может показаться, что возможность pthread_create() ), однако это не так. Во-первых, начальный pthread_join(),
Если приложение заботится об аккуратном освобождении памяти, то для всех PTHREAD_CREATE_JOINABLE, следует предусмотреть вызов либо pthread_join(), либо pthread_detach().
В качестве примера многопотоковой программы приведем серверную часть рассматривавшегося в курсе [1] приложения, копирующего строки со стандартного ввода на стандартный вывод с "прокачиванием" их через потоковые сокеты (см. листинг 1.38).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его серверным), */
/* принимающего запросы на установления соединения и */
/* запускающего потоки управления для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая (и единственная) функция потоков управления, */
/* обслуживающих запросы на копирование строк, */
/* поступающих из сокета */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *srv_thread_start (void *ad) {
FILE *fpad; /* Поток данных, соответствующий */
/* дескриптору ad */
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Структура для записи адреса */
struct sockaddr_in sai;
/* Длина адреса */
socklen_t sai_len = sizeof (struct sockaddr_in);
/* Опросим адрес партнера по общению (передающего сокета) */
if (getpeername ((int) ad, (struct sockaddr *) sai,
sai_len) < 0) {
perror ("GETPEERNAME");
return (NULL);
}
/* По файловому дескриптору ad сформируем */
/* буферизованный поток данных */
if ((fpad = fdopen ((int) ad, "r")) == NULL) {
perror ("FDOPEN");
return (NULL);
}
/* Цикл чтения строк из сокета */
/* и выдачи их на стандартный вывод */
while (fgets (line, sizeof (line), fpad) != NULL) {
printf ("Вы ввели и отправили с адреса %s, "
"порт %d :", inet_ntoa (sai.sin_addr),
ntohs (sai.sin_port));
fputs (line, stdout);
}
/* Закрытие соединения */
shutdown ((int) ad, SHUT_RD);
(void) fclose (fpad);
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* В функции main() принимаются запросы на установление */
/* соединения и запускаются потоки управления для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Буфер для принимаемых строк */
struct addrinfo hints = {AI_PASSIVE, AF_INET,
SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};
/* Указатель – выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
pthread_attr_t patob; /* Атрибутный объект для создания */
/* потоков управления */
pthread_t adt_id; /* Идентификатор обслуживающего потока управления */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM,
IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler */
/* на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints,
addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n",
gai_strerror (res));
return (2);
}
if (bind (sd, addr_res->ai_addr,
addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (3);
}
/* Можно освободить память, которую запрашивала */
/* функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (4);
}
/* Инициализируем атрибутный объект потоков управления */
if ((errno = pthread_attr_init (patob)) != 0) {
perror ("PTHREAD_ATTR_INIT");
return (errno);
}
/* Потоки управления будем создавать обособленными */
(void) pthread_attr_setdetachstate (patob,
PTHREAD_CREATE_DETACHED);
/* Цикл приема соединений и запуска */
/* обслуживающих потоков управления */
while (1) {
/* Примем соединение. */
/* Адрес партнера по общению нас */
/* в данном случае не интересует */
if ((ad = accept (sd, NULL, NULL)) < 0) {
perror ("ACCEPT");
return (6);
}
/* Запустим обслуживающий поток управления */
if ((errno = pthread_create (adt_id, patob,
srv_thread_start,(void *) ad)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
}
return (0);
}
Многопотоковая реализация в данном случае уместнее многопроцессной: она и выглядит проще (поскольку
Можно надеяться, что и следующая программа (см. листинг 1.39), реализующая идею обработки данных с контролем времени, подтверждает, что применение
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа вызывает функции обработки в рамках */
/* порождаемых потоков управления и контролирует время */
/* их выполнения с помощью интервального таймера */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
/* Период интервального таймера (в секундах) */
#define IT_PERIOD 1
static pthread_t cthread_id; /* Идентификатор текущего */
/* потока управления, */
/* обрабатывающего данные */
static int in_proc_data = 0; /* Признак активности */
/* потока обработки данных */
static double s; /* Результат функций */
/* обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция обработки срабатывания таймера реального */
/* времени (сигнал SIGALRM) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_sigalrm (int dummy) {
if (in_proc_data) {
/* Не имеет значения, какой поток обрабатывает сигнал */
/* и заказывает терминирование (быть может, себя) */
(void) pthread_cancel (cthread_id);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Обработчик завершения потока управления. */
/* Сбрасывает признак активности потока обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_cleanup_handler (void *arg) {
in_proc_data = (int) arg;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока управления, обрабатывающего */
/* данные. Аргумент – указатель на функцию обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_func (void *proc_data_func) {
/* Поместим в стек обработчик завершения */
pthread_cleanup_push (proc_data_cleanup_handler, 0);
in_proc_data = 1; /* Время пошло ... */
/* На время выполнения функции обработки данных установим */
/* асинхронный тип терминирования, иначе оно не сработает */
(void) pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS,
NULL);
/* Выполним функцию обработки данных */
((void (*) (void)) (proc_data_func)) ();
/* Установим отложенный тип терминирования, */
/* иначе изъятие обработчика из стека */
/* будет небезопасным действием */
(void) pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED,
NULL);
/* Выполним обработчик завершения и удалим его из стека */
pthread_cleanup_pop (1);
return (NULL);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Первая функция обработки данных (вычисляет ln (2)) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_1 (void) {
double d = 1;
int i;
s = 0;
for (i = 1; i <= 100000000; i++) {
s += d / i;
d = -d;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Вторая функция обработки данных (вычисляет sqrt (2))*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
static void proc_data_2 (void) {
s = 1;
do {
s = (s + 2 / s) * 0.5;
} while ((s * s – 2) > 0.000000001);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() задает способ обработки сигнала SIGALRM, */
/* взводит периодический таймер реального времени */
/* и запускает в цикле потоки обработки данных */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
/* Массив указателей на функции обработки данных */
void (*fptrs []) (void) = {proc_data_1,
proc_data_2, NULL};
/* Указатель на указатель на */
/* текущую функцию обработки данных */
void (**tfptr) (void);
void *pstat; /* Статус завершения потока */
/* обработки данных */
struct itimerval itvl;
struct sigaction sact;
int i;
/* Установим реакцию на сигнал SIGALRM */
sact.sa_handler = proc_sigalrm;
sact.sa_flags = 0;
(void) sigemptyset (sact.sa_mask);
if (sigaction (SIGALRM, sact, NULL) < 0) {
perror ("SIGACTION");
return (1);
}
/* Сделаем таймер реального времени периодическим */
itvl.it_interval.tv_sec = IT_PERIOD;
itvl.it_interval.tv_usec = 0;
/* Цикл запуска потоков обработки данных. */
/* Выполним его дважды */
for (i = 0; i < 2; i++) {
for (tfptr = fptrs; *tfptr != NULL; tfptr++) {
/* Взведем интервальный таймер реального времени */
itvl.it_value.tv_sec = IT_PERIOD;
itvl.it_value.tv_usec = 0;
if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
perror ("SETITIMER");
return (2);
}
/* Создадим поток обработки данных, */
/* затем дождемся его завершения */
if ((errno = pthread_create (cthread_id, NULL,
start_func, (void *) *tfptr)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
if ((errno = pthread_join (cthread_id,
pstat)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
if (pstat == PTHREAD_CANCELED) {
printf ("Частичный результат функции "
"обработки данных: %g\n", s);
} else {
printf ("Полный результат функции "
"обработки данных: %g\n", s);
}
}
}
return 0;
}
Возможные результаты работы приведенной программы показаны на листинге 1.40.
Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421 Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421
К сожалению, там, где есть недетерминированность и асинхронность, без тонкостей все равно не обойтись. Мы обратим внимание на три из них. Во-первых, возможно SIGALRM до того, как завершится (или даже начнется) первый вызов pthread_create() и будет инициализирована переменная cthread_id. Чтобы не допустить cthread_id заведомо инициализирована.
Во-вторых, не имеет значения, в контексте какого из
В-третьих, поскольку функции обработки данных не содержат точек
Как мы уже упоминали,
На листингах 1.41 и 1.42 показана программа, которая в цикле порождает практически пустые time -p. Даже если сделать , времена получатся довольно большими (см. листинг 1.44) в сравнении с аналогичными данными для варианта с
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define N 10000
int main (void) {
int i;
for (i = 0; i < N; i++) {
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
/* Порожденный процесс */
(void) execl ("./dummy", "dummy",
(char *) 0);
exit (0);
default:
/* Родительский процесс */
(void) wait (NULL);
}
}
return 0;
}
int main (void) {
return 0;
}
real 34.97 user 12.36 sys 22.61
real 11.49 user 2.38 sys 9.11
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define N 10000
static void *thread_start (void *arg) {
pthread_exit (arg);
}
int main (void) {
pthread_t thread_id;
int i;
for (i = 0; i < N; i++) {
if ((errno = pthread_create (
thread_id, NULL,
thread_start, NULL)) != 0) {
perror ("PTHREAD_CREATE");
return (errno);
}
if ((errno = pthread_join (
thread_id, NULL)) != 0) {
perror ("PTHREAD_JOIN");
return (errno);
}
}
return (0);
}
real 2.08 user 0.52 sys 1.56
В первом приближении можно считать, что
Можно сделать вывод, что
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.