Поток (нить, легковесный процесс) - единица планирования времени в рамках одного процесса.
Все потоки в рамках одного процесса разделяют общее адресное пространство и открытые файловые дескрипторы.
Для каждого потока предусмотрен свой отдельный стек фиксированного размера, который располагается в общем адресном пространстве. В конце стека для каждого потока обычно присутствует небольшой участок памяти (Guad Page), предназначенный для того, чтобы предотвратить ситуацию перезаписи данных другого потока в результате, например, его переполнения.
В каждом процессе существует как минимум один поток, выпонение которого начинается с функции _start
.
В отличии от обычных процессов, которые имеют иерархическую структуру "родитель-ребенок", все потоки являются равнозначными.
Стандартом для UNIX-систем является POSIX Threads API. В системе Linux (как и во FreeBSD), ввиду фрагментации стандартной библиотеки, компоновка программ должна проводиться с опцией компилятора -pthread
.
В отличии от большинства других функций POSIX, в случае ошибки, функции из pthread не прописывают их код в переменную errno
, а возвращают различные целочисленные значения, отличные от 0
, которые соответствуют определенным ошибкам.
int pthread_create(
// указатель на переменную-результат
pthread_t *thread,
// опционально: параметры нового потока,
// может быть NULL
const pthread_attr_t *attr,
// функция, которая будет выполняться
(void*)(*function)(void*),
// аргумент, который передается в функцию
void *arg
);
Функция pthread_create
создает новый поток, и сразу же запускает в нем на выполнение функцию, которая передана в качестве аргумента.
Выполняемая функция должна принимать единственный аргумент размером с машинное слово (void*
), и этот аргумент передается одновременно с созданием потока. Возвращаемое значение выполняемой функции можно будет получить после её выполнения о ожидания завершения потока.
Поток завершается в тот момент, когда завершается выполнение функции, либо пока не будет вызван аналог exit
для потока - функция pthread_exit
.
Возвращаемые значения размером больше одного машинного слова, которые являются результатом работы потока, не могут быть размещены в стеке, поскольку стек будет уничтожен при завершении работы функции.
Дождаться завершения потока и получить результат можно с помощью функции pthread_join
int pthread_join(
// поток, который нужно ждать
pthread_t thread,
// указатель на результат работы функции,
// либо NULL, если он не интересен
(void*) *retval
);
Функция pthread_join
ожидает завершения работы определенного потока, и получает результат работы функции.
Возможна ситуация, приводящая к deadlock'у, когда два потока вызывают друг для друга ожидание. Функция pthread_join
проверяет эту ситуацию, и завершается с ошибкой (не блокируя выполнение).
pthread_t a;
pthread_t b;
void* thread_func_a(void *) {
sleep(1);
pthread_join(b, NULL);
}
void* thread_func_b(void *) {
sleep(1);
pthread_join(a, NULL);
}
// Bug: Deadlock, but detected
pthread_create(&a, NULL, thread_func_a, 0);
pthread_create(&b, NULL, thread_func_b, 0);
Такая проверка возможна только при попытке ожидать поток, который уже ожидает поток, пытающийся вызвать pthread_join
. В случае нескольких потоков, которые косвенно ожидают друг друга, такая диагностика невозможна, и приведет к deadlock'у.
Функция pthread_cancel
принудительно завершает работу потока, если поток явно это не запретил с помощью функции pthread_setcancelstate
.
int pthread_cancel(
// поток, который нужно прибить
pthread_t thread
);
Результатом работы функции, который будет передан в pthread_join
будет специальное значение PTHREAD_CANCELED
.
В системе Linux остановка потоков реализована через отправку процессом самому себе сигнала реального времени с номером 32
.
Принудительное завершение потока вовсе не означает, что поток будет немедленно остановлен. Функция pthread_cancel
только проставляет флаг остановки, и этот флаг может быть проверен только во время определенного набора системных вызовов и функций стандартной библиотеки, которые называются Cancelation Points.
Полный список функций, которые могут быть прерваны, перечислен в разделе 7
man-страницы pthreads
.
Некоторые системы, в том числе Linux, позволяют принудительно завершить поток даже вне Cancelation Points. Для этого поток должен вызывать функцию pthread_setcanceltype
с параметром PTHREAD_CANCEL_ASYNCHRONOUS
. После этого завершение потока будет осуществляться на уровне планировщика заданий.
Атрибуты потока (второй параметрв в pthread_create
) хранятся в структуре pthread_attr_t
, объявление которой является платформо-зависимым, и не регламентируется стандартом POSIX.
Для инициализации атрибутов используется функция pthread_attr_init(pthread_attr_t *attr)
, и кроме того, после использования, структуру атрибутов необходимо уничтожать с помощью pthread_attr_destroy
.
С помощью нескольких функций-сеттеров можно задавать определенные параметры вновь создаваемого потока:
pthread_attr_setstacksize
- установить размер стека для потока. Размер стека должен быть кратен размеру страницы памяти (обычно 4096 байт), и для него определен минимальный размер, определяемый из параметров системыsysconf(_SC_THREAD_STACK_MIN)
или константойPTHREAD_STACK_MIN
из<limits.h>
(в Linux это 16384 байт);pthread_attr_setstackaddr
- указать явным образом адрес размещения памяти, которая будет использована для стека;pthread_attr_setguardsize
- установить размер защитной области после стека (Guard Page). По умолчанию в Linux этот размер равен размеру страницы памяти, но можно явно указать значение 0.