Листинг 32.1. Отмена потока с помощью pthread_cancel()

threads/thread_cancel.c

#include

#include "tlpi_hdr.h"

static void *

threadFunc(void *arg)

{

int j;

printf("New thread started\n"); /* Может быть точкой отмены */

for (j = 1;; j++) {

printf("Loop %d\n", j); /* Может быть точкой отмены */

sleep(1); /* Точка отмены */

}

/* NOTREACHED */

return NULL;

}

int

main(int argc, char *argv[])

{

pthread_t thr;

int s;

void *res;

s = pthread_create(&thr, NULL, threadFunc, NULL);

if (s!= 0)

errExitEN(s, "pthread_create");

sleep(3); /* Позволяем новому потоку проработать некоторое время */

s = pthread_cancel(thr);

if (s!= 0)

errExitEN(s, "pthread_cancel");

s = pthread_join(thr, &res);

if (s!= 0)

errExitEN(s, "pthread_join");

if (res == PTHREAD_CANCELED)

printf("Thread was canceled\n");

else

printf("Thread was not canceled (should not happen!)\n");

exit(EXIT_SUCCESS);

}

threads/thread_cancel.c

32.4. Проверка возможности отмены потока

В листинге 32.1 поток, созданный функцией main(), принял запрос отмены, поскольку он выполнял функцию, которая является точкой отмены (sleep() является ею наверняка, а printf() может быть таковой). Но представьте, что поток выполняет цикл, который не содержит точек отмены (например, состоящий из сплошных вычислений). В этом случае запрос отмены никогда не будет удовлетворен.

Единственное назначение функции pthread_testcancel() — быть точкой отмены. Если отмена отложена во время вызова данной функции, это означает, что вызывающий поток завершен.

#include

void pthread_testcancel(void);

Поток, который не содержит точек отмены, может время от времени вызывать pthread_testcancel(), чтобы обеспечить своевременный ответ на запрос отмены, отправляемый другим потоком.

32.5. Обработчики, освобождающие ресурсы

Если поток с отложенной отменой просто завершается при достижении точки отмены, разделяемые переменные и объекты библиотеки Pthreads (например, мьютексы) могут остаться в несогласованном состоянии, что может привести к получению некорректных результатов, взаимным блокировкам или аварийному завершению оставшихся потоков. Чтобы обойти эту проблему, поток может установить один или несколько обработчиков для освобождения ресурсов — это функции, которые автоматически выполняются при отмене потока. Эти обработчики могут выполнять перед завершением потока такие задачи, как изменение значений глобальных переменных и открытие мьютексов.

Каждый поток может иметь стек обработчиков для очистки ресурсов. При отмене потока эти обработчики выполняются снизу вверх; то есть обработчик, установленный позже других, вызывается первым. Когда все обработчики выполнились, поток завершается.

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

#include

void pthread_cleanup_push(void (*routine)(void*), void *arg);

void pthread_cleanup_pop(int execute);

Вызов pthread_cleanup_push() добавляет функцию, чей адрес указан в аргументе routine, на вершину стека обработчиков для очистки ресурсов потока. Аргумент routine указывает на функцию следующего вида:

void

routine(void *arg)

{

/* Код для очистки ресурсов */

}

Значение arg, переданное в pthread_cleanup_push(), предоставляется в виде аргумента вызываемого обработчика. Тип этого аргумента — void *, но его можно привести и к другому типу данных.

Обычно очистка требуется, только если поток был отменен во время выполнения определенного участка кода. Если поток проходит этот участок без отмены, очищать ресурсы больше не нужно. В связи с этим pthread_cleanup_push() имеет сопровождающий вызов, pthread_cleanup_pop(), который удаляет функцию на вершине стека обработчиков для очистки ресурсов. Если аргумент execute не равен 0, обработчик тоже выполняется. Это может пригодиться в ситуациях, когда нам нужно очистить ресурсы, даже если поток не был отменен.

Здесь мы описываем pthread_cleanup_push() и pthread_cleanup_pop() как функции, но стандарт SUSv3 позволяет реализовывать их в виде макросов, которые разворачиваются в цепочки инструкций, включающих соответственно открывающую ({) и закрывающую (}) скобки. Так происходит в Linux и многих других системах, хотя не все реализации UNIX используют данный подход. Это означает, что в одном и том же лексическом блоке кода каждому pthread_cleanup_push() должен соответствовать pthread_cleanup_pop() (в системах, которые реализованы таким образом, переменные, объявленные между вызовами pthread_cleanup_push() и pthread_cleanup_pop(), будут ограничены этой лексической областью). Например, следующий код является некорректным:

pthread_cleanup_push(func, arg);

if (cond) {

pthread_cleanup_pop(0);

}

Перейти на страницу:

Похожие книги