Skip to content

Latest commit

 

History

History

pthread

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Многопоточность

Общие сведения

Поток (нить, легковесный процесс) - единица планирования времени в рамках одного процесса.

Все потоки в рамках одного процесса разделяют общее адресное пространство и открытые файловые дескрипторы.

Для каждого потока предусмотрен свой отдельный стек фиксированного размера, который располагается в общем адресном пространстве. В конце стека для каждого потока обычно присутствует небольшой участок памяти (Guad Page), предназначенный для того, чтобы предотвратить ситуацию перезаписи данных другого потока в результате, например, его переполнения.

В каждом процессе существует как минимум один поток, выпонение которого начинается с функции _start.

В отличии от обычных процессов, которые имеют иерархическую структуру "родитель-ребенок", все потоки являются равнозначными.

POSIX Threads

Стандартом для 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.