В ходе этого раздела вы изучите
- Создание нитей с атрибутами по умолчанию
- Передачу параметров нити
- Завершение нити
- Ожидание завершения другой нити
- Принудительное завершение нити
- Обработку принудительного завершения нити
Создание нитей с атрибутами по умолчанию
В POSIX Thread API нить создается библиотечной функцией pthread_create(3C).
Параметры этой функции:
pthread_t * thread – Выходной параметр. Указатель на переменную, в которой при успешном завершении будет размещен идентификатор нити.
const pthread_attr_t * attr – Входной параметр. Указатель на структуру, в которой заданы атрибуты нити (рассматривается на следующей лекции). Если этот указатель равен NULL, используются атрибуты по умолчанию.
void *(*start_routine)(void*) – Входной параметр. Указатель на функцию, которая будет запущена во вновь созданной нити.
void * arg – Входной параметр. Значение, которое будет передано в качестве параметра start_routine.
Возвращаемое значение
0 при успешном завершении
Код ошибки при неудачном завершении
Коды ошибок
Значения кодов ошибок определены в виде символов препроцессора в файле errno.h
EAGAIN – системе не хватает ресурсов для создания нити. Возможно, не хватает памяти под стек, исчерпан архитектурный лимит на количество нитей в процессе (PTHREAD_THREADS_MAX) либо административное ограничение на количество нитей. Как и у остальных системных вызовов, код EAGAIN означает, что повторный вызов функции с теми же параметрами может не привести к ошибке.
EINVAL – один из параметров имеет недопустимое значение. Например, указатель на start_routine указывает на страницу памяти, исполнение которой запрещено.
EPERM – вы не имеете полномочий для исполнения нити с заданными атрибутами. Например, вы не можете установить заданные в структуре attr класс планирования или приоритет.
Пример 1
Важное замечание
Параметр функции нити описан как void *, но библиотека никогда не пытается обращаться к нему как к указателю. Поэтому этот указатель можно использовать либо как ссылку на структуру (блок параметров нити), либо для передачи скалярного значения.
При передаче структур данных в качестве параметра нужно проявлять осторожность.
Во первых, не следует передавать структуры данных, размещенные в стеке родительской нити, то есть переменные с классом памяти auto и блоки памяти, размещенные при помощи alloca(3C). Действительно, если ваша нить вернет управление из текущей функции или вообще завершится до того, как созданная нить начнет работать с блоком параметров, получится, что вы передали в качестве параметра висячую ссылку.
Во вторых, при передаче структур данных, размещенных при помощи malloc(3C) или оператора new языка C++, необходимо решить вопрос о том, кто будет освобождать эту структуру. Если структура не будет освобождена при помощи free(3C) или оператора delete, это приведет к утечке памяти. Существует несколько решений этого вопроса, приемлемых в разных ситуациях.
Одно из решений состоит в том, что родитель размещает структуру, а созданная нить ее освобождает. Но обычно это считается дурным тоном при программировании на C/C++ (хорошим тоном считается, чтобы вызываемая функция не знала, каким образом выделена память под переданные ей параметры).
Другое решение состоит в том, что родитель размещает структуру, дожидается завершения потомка при помощи вызова pthread_join(3C) и освобождает структуру. В рамках этого подхода можно передавать как структуры данных, созданные при помощи malloc(3C), так и структуры данных, размещенные в стеке родителя. Проблема этого подхода состоит в том, что если родитель будет принудительно завершен при помощи pthread_cancel(3С), он может не дождаться завершения своих потомков, а это приведет либо к утечке памяти, либо к висячим ссылкам.
В программах с небольшим количеством нитей часто передают в качестве параметра указатели на статические переменные, но в больших программах с большим числом нитей это неприемлемо.
Завершение нити
Для завершения нити используется функция pthread_exit(3C). Эта функция всегда завершается успешно и принимает единственный параметр, указатель на код возврата типа void *. Как и в случае с pthread_create(3C), библиотека никогда не пытается обращаться к значению этого параметра как к указателю, поэтому его можно использовать для передачи скалярных значений.
Другой способ завершения нити – это возврат управления из функции start_routine при помощи оператора return. В действительности, этот способ полностью эквивалентен вызову pthread_exit(3C). При запуске нити функция start_routine вызывается библиотекой, и при возврате передает управление в код библиотеки. Этот код и содержит вызов pthread_exit(3C).