Мы рассмотрим низкоуровневый интерфейс работы с нитями - POSIX Thread.
Для использования POSIX Thread необходимо подключить заголовочный файл:
#include <pthread.h>
При компиляции программы необходимо использовать опцию -pthread
g++ -std=gnu++14 -Wall -O2 -pthread prog.cpp -o prog
или
g++ -std=gnu11 -Wall -O2 -pthread prog.c -o prog
Основная функция нити должна иметь вид
void *thread_func(void *ptr);
Аргумент ptr
передается без изменений из создающей нити. В этом аргументе можно передавать указатель на структуру параметров нити
или даже, используя преобразование типов, передавать целые числа (например, номер нити).
Однако тип int
и тип указателя могут иметь разные размеры. На помощь приходит либо тип size_t
, либо тип intptr_t
,
который может послужить промежуточным типом для преобразования из целого числа в указатель и обратно.
Возвращаемое значение нити передается без изменений в ту нить, которая будет выполнять ожидание завершения нити.
Нить завершается, когда завершается основная функция нити. Нить может быть завершена с помощью вызова функции
void pthread_exit(void *ptr);
Внимание! Вызовы exit
, abort
, либо получение фатального сигнала ошибки работы с памятью вызывает завершение всего процесса,
а не отдельной нити!
Нить создается с помощью вызова pthread_create
.
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
Функция возвращает 0 при успешном завершении и значение кода ошибки (положительное число)
при ошибке. Возможные коды ошибок: EAGAIN
, EINVAL
, EPERM
, они описаны в <errno.h>
.
Обратите внимание, переменная errno
в случае ошибки не модифицируется.
В случае успешного создания нити нить будет готова к выполнению и начнет выполняться, когда планировщик операционной системы передаст ей управление.
В параметре thread
передается указатель на переменную типа pthread_t
, в которую будет записан
идентификатор созданной нити.
Идентификатор нити (тип pthread_t
) - это некоторый "непрозрачный" указатель. Ему можно присваивать значение 0,
либо его можно сравнивать на равенство или неравенство с другим идентификатором нити.
Параметр attr
позволяет задавать атрибуты создаваемой нити, такие как размер стека, приоритет, флаги планирования и т. п.
Если нить создается со значениями атрибутов по умолчанию, параметр attr
может быть передан равным NULL
.
Параметр start_routine
- это основная функция нити.
Параметр arg
передается в основную функцию нити без изменений.
Ненулевой параметр attr
может потребоваться, если нужно задать другой размер стека нити, чем по умолчанию.
По умолчанию размер стека нити берется из ограничения на размер стека процесса (ulimit -s
или RLIMIT_STACK
в getrlimit
). Размер стека нельзя задать менее чем минимальный поддерживаемый размер стека для данной
платформы. Минимальный размер стека для нити можно получить с помощью вызова sysconf(_SC_THREAD_STACK_MIN)
.
Нити внутри процесса не образуют иерархии "отец-сын". Любая нить может ждать завершения выполнения любой другой нити.
int pthread_join(pthread_t thread, void **retval);
Функция возвращает 0 при успешном завершении, либо код ошибки при ошибке. Значение переменной errno никогда не изменяется.
В параметре thread
передается идентификатор нити. Если параметр retval
не равен NULL
, то по этому указателю будет сохранено
значение, которое завершившаяся нить вернула с помощью возвращаемого значения функции нити, либо с помощью pthread_exit
.
Нить можно ожидать только один раз. Повторная попытка ожидания нити, ожидание которой уже было выполнено, скорее всего приведет к ошибке "Segmentation fault" и аварийному завершению процесса.
В библиотеке pthread отсутствуют функции, которые позволили бы ждать любую нить из созданных. Если возникла необходимость использования подобной функции, необходимо пересматривать архитектуру приложения, чтобы такие ситуации исключить.
Переменную, локальную для нити, можно определить с помощью
- В C11 с помощью ключевого слова _Thread_local
_Thread_local int count;
- В C++11 thread_local
thread_local int count;
С точки зрения программы на C или C++ это - глобальная переменная, доступная во всех функциях, находящихся в единице компиляции ниже точки определения.
Каждая нить имеет свою копию переменных, локальных для нити. При создании нити эти переменные копируются из специальной секции исполняемого файла.
Поэтому их начальные значения будут теми, которые прописаны в исполняемом файле, а не теми, которые в момент создания были у
нити, которая выполняет pthread_create
. Каждая нить может модифицировать свои переменные, локальные для нити. Эти изменения
не отразятся на других нитях.
Тем не менее, поскольку все нити работают в общем адресном пространства, можно передать адрес переменной, локальной для нити, в другие нити, и другие нити смогут читать и модифицировать эту переменную.
При работе с глобальными переменными следует помнить о состояниях гонок и атомарности.