PTHREAD_CLEANUP_PUSH(3)

ИМЯ
pthread_cleanup_push, pthread_cleanup_pop - помещает и выталкивает очищающие обработчики при отмене нити

ОБЗОР
#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *),
                          void *arg);
void pthread_cleanup_pop(int execute);

Компилируется и компонуется вместе с -pthread.

ОПИСАНИЕ
Данные функции управляют стеком нити, вызывающим очищающие обработчики при отмене нити. Очищающий обработчик — это функция, которая автоматически вызывается при отмене нити (или в при других обстоятельствах, описанных ниже); это может быть, например, отмена блокировки мьютекса, при которой он становится доступным другим нитям процесса.

Функция pthread_cleanup_push() помещает обработчик routine наверх стека очищающих обработчиков. Позднее, при вызове routine, ему будет передан arg в качестве аргумента.

Функция pthread_cleanup_pop() удаляет обработчик с вершины стека очищающих обработчиков, и, возможно, выполняет его, если execute не равно нулю.

Очищающий обработчик при отмене выталкивается из стека и выполняется при следующих условиях:

1.  Когда нить отменяется, все очищающие обработчики из стека выталкиваются и вызываются в обратном порядке их помещения в стек.

2.  Когда нить завершается вызовом pthread_exit(3), все очищающие обработчики выполняются как описано в предыдущем пункте (очищающие обработчики не вызываются, если нить завершается возвратом из начальной функции нити).

3.  Когда нить вызывает pthread_cleanup_pop() с ненулевым значением аргумента execute, самый верхний очищающий обработчик выталкивается и выполняется.

В POSIX.1 допускается реализация pthread_cleanup_push() и pthread_cleanup_pop() в виде макросов, которые раскрывают текст, содержащий '{' и '}', соответственно. По этой причине, вызывающий должен убедиться, что вызовы этих функций используются парно в одной функции и на одном лексическом уровне вложенности (другими словами, очищающий обработчик устанавливается только во время выполнения определённой секции кода).

Вызов longjmp(3) (siglongjmp(3)) приводит к непредсказуемым результатам, если был сделан любой вызов в pthread_cleanup_push() или pthread_cleanup_pop() без соответствующего вызову пары, так как буфер перехода (jump buffer) был заполнен setjmp(3) (sigsetjmp(3)). Подобным образом, вызов longjmp(3) (siglongjmp(3)) изнутри очищающего обработчика приводит к непредсказуемым результатам, если буфер перехода также не был заполнен setjmp(3) (sigsetjmp(3)) внутри обработчика.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Данные функции не возвращают никаких значений.

ОШИБКИ
Эти функции не вызывают ошибок.

АТРИБУТЫ
Описание терминов данного раздела смотрите в attributes(7).
Интерфейс | Атрибут | Значение
pthread_cleanup_push(), pthread_cleanup_pop() | Безвредность в нитях | MT-Safe

СООТВЕТСТВИЕ СТАНДАРТАМ
POSIX.1-2001, POSIX.1-2008.

ЗАМЕЧАНИЯ
В Linux, функции pthread_cleanup_push() и pthread_cleanup_pop() реализованы в виде макросов, которые расширяются до текста, содержащего '{' и '}', соответственно. Это означает, что переменные, объявленные внутри области парных вызовов этих функций, будут видимы внутри только этой области.

В POSIX.1 сказано, что результат использования return, break, continue или goto для преждевременного оставления блока, окружающего pthread_cleanup_push() и pthread_cleanup_pop(), не определён. В переносимых приложениях не нужно так делать.

ПРИМЕР
Далее показан простой пример использования функций, описанных на этой странице. Программа создаёт нить, которая выполняет цикл обрамлённый pthread_cleanup_push() и pthread_cleanup_pop(). В этом цикле каждую секунду увеличивается глобальная переменная cnt. В зависимости от аргументов командной строки главная нить посылает другой нити запрос на отмену или изменяет глобальную переменную, что заставляет другую нить выйти из цикла и завершить работу (с помощью return).

В этом сеансе главная нить посылает запрос отмены другой нити:

    $ ./a.out
    Запущена новая нить
    cnt = 0
    cnt = 1
    Отменяем нить
    Вызван очищающий обработчик
    Нить отменена; cnt = 0

Здесь мы видим, что нить была отменена и что был вызван очищающий обработчик и он сбросил значение глобальной переменной cnt в 0.

В следующем сеансе главная программа изменяет глобальную переменную, что вызывает штатное завершение другой нити:

    $ ./a.out x
    Запущена новая нить
    cnt = 0
    cnt = 1
    Нить завершилась штатным образом; cnt = 2

Здесь мы видим, что очищающий обработчик не вызывался (так как cleanup_pop_arg равно 0), и поэтому значение cnt не сброшено.

В следующем сеансе главная программа изменяет глобальную переменную, что вызывает штатное завершение другой нити, передаёт ненулевое значение в cleanup_pop_arg:

    $ ./a.out x 1
    Запущена новая нить
    cnt = 0
    cnt = 1
    Вызван очищающий обработчик
    Нить завершилась штатным образом; cnt = 0

Здесь мы видим, что хотя нить не отменена, всё же вызван очищающий обработчик, так как в pthread_cleanup_pop() передан ненулевой аргумент.

  Исходный код программы
    #include <pthread.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #define handle_error_en(en, msg) \
            do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
    static int done = 0;
    static int cleanup_pop_arg = 0;
    static int cnt = 0;
    static void
    cleanup_handler(void *arg)
    {
        printf("Вызван очищающий обработчик\n");
        cnt = 0;
    }
    static void *
    thread_start(void *arg)
    {
        time_t start, curr;
        printf("Запущена новая нить\n");
        pthread_cleanup_push(cleanup_handler, NULL);
        curr = start = time(NULL);
        while (!done) {
            pthread_testcancel();           /* точка отмены */
            if (curr < time(NULL)) {
                curr = time(NULL);
                printf("cnt = %d\n", cnt);  /* точка отмены */
                cnt++;
            }
        }
        pthread_cleanup_pop(cleanup_pop_arg);
        return NULL;
    }
    int
    main(int argc, char *argv[])
    {
        pthread_t thr;
        int s;
        void *res;
        s = pthread_create(&thr, NULL, thread_start, NULL);
        if (s != 0)
            handle_error_en(s, "pthread_create");
        sleep(2);           /* даём время нити запуститься */
        if (argc > 1) {
            if (argc > 2)
                cleanup_pop_arg = atoi(argv[2]);
            done = 1;
        } else {
            printf("Отменяем нить\n");
            s = pthread_cancel(thr);
            if (s != 0)
                handle_error_en(s, "pthread_cancel");
        }
        s = pthread_join(thr, &res);
        if (s != 0)
            handle_error_en(s, "pthread_join");
        if (res == PTHREAD_CANCELED)
            printf("Нить была отменена; cnt = %d\n", cnt);
        else
            printf("Нить завершилась штатным образом; cnt = %d\n", cnt);
        exit(EXIT_SUCCESS);
    }

СМОТРИТЕ ТАКЖЕ
pthread_cancel(3), pthread_cleanup_push_defer_np(3), pthread_setcancelstate(3), pthread_testcancel(3), pthreads(7)