Уничтожение (отмена) потока

We use cookies. Read the Privacy and Cookie Policy

Уничтожение (отмена) потока

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

Если для процесса в перечень «опасных» (с точки зрения завершения) объектов включаются только объекты со временем жизни выше уровня процесса (их число достаточно ограничено), то для потока в число таких объектов включаются уже все объекты со временем жизни процесса (process-persistent). Завершающийся (покидающий процесс) поток обязан оставить все объекты процесса в состоянии, пригодном для их дальнейшего использования другими потоками процесса.

Далее мы подробно рассмотрим то множество предосторожностей, которыми «обложена» отмена потока. Однако именно по причине их «множества» стоит сформулировать краткое правило: не пытайтесь завершать поток извне его функции потока, если для этого нет в высшей степени обоснованной необходимости (а такая необходимость действительно бывает, но крайне редко). Даже в крайнем случае следует рассмотреть возможность вместо отмены потока послать ему сигнал (даже не только «сигнал UNIX», а в более широком смысле — «некоторое сообщение»), который, обрабатываясь в контексте потока, после корректных завершающих действий вызовет его завершение. (Как обращаться с сигналами в потоке, будет детально рассмотрено позже.)

Для отмены (принудительного завершения) потока используется вызов:

int pthread_cancel(pthread_t thread);

где в качестве параметра thread указывается TID отменяемого потока. Однако этот вызов не отменяет поток, а только запрашивает завершение потока. В зависимости от статуса отмены, который мы сейчас рассмотрим, поток может перейти (или нет) к действию завершения, которое состоит в том, что:

• выполняются все процедуры завершения, занесенные ранее в стек завершения вызовами pthread_cleanup_push();

• выполняются деструкторы собственных данных потока;

• отменяемый поток завершается;

• процесс отмены — асинхронный с точки зрения вызывающего pthread_cancel() кода, поэтому вызывающий отмену поток должен дождаться завершения потока на вызове pthread_join().

Прежде всего, поток может вообще отказаться выполнять любые отмены, вызвав из своей функции потока:

int pthread_setcancelstate(int state, int* oldstate);

где state и oldstate — устанавливаемое и установленное ранее (возвращаемое вызовом) состояния отмены потока, которые могут принимать значения PTHREAD_CANCEL_DISABLE либо PTHREAD_CANCEL_ENABLE. (Естественно, как и во многих функциях с подобным прототипом, значением oldstate может быть NULL, и тогда нам не нужно возвращать ранее установленное состояние.)

Далее, даже если для потока установлено состояние завершаемости (также называемое «состоянием отмены») PTHREAD_CANCEL_ENABLE (это значение по умолчанию при создании потока), поток может переопределить еще и тип отмены, вызвав:

int pthread_setcanceltype(int type, int* oldtype);

где type и oldtype — как и в предыдущем случае, новое и ранее установленное значения типа отмены потока, которые могут принимать значения PTHREAD_CANCEL_ASYNCHRONOUS (асинхронный по отмене поток) либо PTHREAD_CANCEL_DEFERRED (синхронный по отмене поток). Значением по умолчанию, устанавливаемым при создании потока, является PTHREAD_CANCEL_DEFERRED, хотя предписываемым POSIX умолчанием является PTHREAD_CANCEL_ASYNCHRONOUS.

Обе рассмотренные функции установок[23] параметров отмены при успешном выполнении возвращают значение EOK.

Итак, действия потока на запрос его завершения будут определяться текущей комбинацией двух установленных для него параметров: состоянием и типом отмены.

Теперь о том, чем же отличается отмена асинхронно и синхронно завершаемых потоков. Поток с асинхронным типом отмены (установленный с PTHREAD_CANCEL_ASYNCHRONOUS) может быть отменен в любой произвольный момент времени, то есть он всегда «свободен» для отмены и отмена производится немедленно. Поток с синхронным типом отмены (установленный с PTHREAD_CANCEL_DEFERRED) может быть остановлен только в тех точках выполнения потока, когда ему «удобно», и соответствующие места в программе называются точками отмены. При поступлении запроса на отмену такого потока (после выполнения извне pthread_cancel()) запрос помещается в очередь, а процесс отмены активизируется только после того, как отменяемый поток в ходе своего выполнения достигнет очередной точки отмены. Как определяются (создаются) точки отмены в коде потока? Для этого служит функция:

void pthread_testcancel(void);

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

Примечание

(Очень важно!) Достаточно много библиотечных функций могут сами устанавливать точки отмены. Более того, такие функции могут косвенно вызываться из других функций в программе и тем самым неявно устанавливать точки отмены. Информацию о таких функциях следует искать в справочной man-странице по функции pthread_testcancel(). В результате этого эффекта можно получить отмену потока не в той точке, которую вы считаете безопасной и которую явно отмечаете вызовом pthread_testcancel(), а ранее этой точки — когда будет вызвана одна из таких функций. А это, очевидно, вовсе не то, на что вы рассчитывали!

Если состояние отмены потока, как это описывалось ранее, установлено в PTHREAD_CANCEL_DISABLE, то никакая расстановка точек отмены не имеет эффекта и поток остается неотменяемым.

Покажем, как могут быть использованы все эти предосторожности в коде функции потока, чтобы сделать код безопасным с позиции возможной асинхронной отмены потока извне:

void* function(void* data) {

 int state;

 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state);

 // ... здесь выполняется инициализация ...

 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

 pthread_setcancelstate(&state, NULL);

 while (true) {

  struct blockdata *blk = new blockdata;

  // ... обработка блока данных blk ...

  delete blk;

  pthread_testcancel();

 }

}

...

pthread_t tid;

...

pthread_create(&tid, NULL, function, NULL);

...

pthread_cancel(tid); // отмена потока

void* res;

pthread_join(tid, &res); // ожидание отмены

if (res != PTHREAD_CANCELED)

cout << "Что-то не так!" << endl;

Наконец, в QNX (но не в POSIX) существует вызов, подобный pthread_cancel(), принудительно отменяющий поток независимо от его установок («желания»):

int pthread_abort(pthread_t thread);

В отличие от pthread_cancel(), этот вызов принудительно и немедленно отменяет поток. Кроме того, никакие процедуры завершения и деструкторы собственных данных потока не выполняются. Очевидно, что в результате такого «завершения» состояния объектов процесса будут просто неопределенными, поэтому такой вызов крайне опасен. При таком способе отмены в программный код, ожидающий завершения на pthread_join(), в качестве результата завершения возвращается константа (тип void*) PTHREAD_ABORTED (аналогично возвращается константа PTHREAD_CANCELED при выполнении pthread_cancel()).

Но и этих мер безопасности недостаточно на все случаи жизни, поэтому механизм потоков предусматривает еще один уровень (механизм) страховки.

Данный текст является ознакомительным фрагментом.