Ожидание завершения нити

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

В операционной системе OS/2 функция ожидания завершения нити называется DosWaitThread и имеет прототип

APIRET DosWaitThread(PTID ptid, ULONG option).

Эта функция в данной операционной системе предназначена для целого спектра действий. Она может быть использована для ожидания завершения конкретной нити того же процесса, заданной идентификатором в первом аргументе. (Отметим, что этот аргумент – возвращаемый!) При этом второй аргумент должен быть задан символической константой DCWW_WAIT или просто ее значением, равным нулю. Она может быть использована для получения информации, завершена ли конкретная нить данного процесса. Тогда второй аргумент должен задаваться константой DCWW_NOWAIT, равной 1. Результаты выполнения функции DosWaitThread при этом выдаются через код возврата. Именно, если указанная в вызове нить завершена, возвращается нулевой код, если не завершена, возвращается код ХХХХ, представляемый символической константой ХХХ_ХХХХХ.

Эта же функция DosWaitThread может быть использована для приостановки выполняющей ее нити до тех пор, пока какая-то другая нить этого же процесса не будет завершена. Для этих целей функция DosWaitThread должна быть вызвана с нулевым значением переменной для идентификатора нити, на которую указывает первый аргумент функции. С этой целью можно использовать последовательность операторов

tid=0; DosWaitThread(&tid, DCWW_WAIT),

где переменная tid определена как имеющая тип TID. В результате такого выполнения функции DosWaitThread будет получено значение идентификатора (в переменной tid – для указанного примера), обозначающего ту нить, которая была завершена после вызова этой функции.

Наконец, эта же функция может быть использована без ожидания для получения информации, какая нить в данном процессе была завершена последней. В этом применении вызов функции должен иметь второй аргумент, равный DCWW_NOWAIT, а первый аргумент быть указателем на нулевое значение переменной для идентификатора нити.

В операционной системе Unix, поддерживающей стандарт POSIX, для ожидания нити служит функция pthread_join, имеющая прототип

int pthread_join(pthread_t tid, void** status).

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

тип_результата restatus;

void *status=&restatus;

. . .

pthread_exit(&restatus); // в конце процедуры нити

. . .

pthread_join(tid, &status);

<анализ значения *status>

либо в простейшем случае может быть использовано явное преобразование типа.

 

#include <stdio.h>

char lbuk[ ]="abcdefghijklmnoprstvuwxy";

pthread_t tid1, tid2, tid3;

 

void procthread1(void *arg)

{int k, j;

for (k=0; k<24; k++) {

// установить вывод в позицию (20,k+1) и синий цвет

printf("\033[%02d;20H",k+1); printf("\033[1;34m");

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

printf("\n");

usleep(1000000);

}

}

 

void procthread2(void *arg)

{int k, j;

for (k=0; k<24; k++) {

//установить вывод в позицию (40,k+1) и зеленый цвет

printf("\033[%02d;40H",k+1); printf("\033[1;32m");

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

printf("\n");

usleep(1000000);

}

}

 

void procthread3(void *arg)

{int k, j, *rc;

for (k=0; k<7; k++) {

// установить вывод в позицию (60,k+1) и красный цвет

printf("\033[%02d;60H",k+1); printf("\033[1;31m");

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

printf("\n");

usleep(1000000);

}

printf("\033[%02d;60H",k+1); printf("\033[1;31m");

printf("Waiting End thread1\n");

pthread_join(tid1, (void**)&rc);

printf("\033[%02d;60H",k+1); // установить вывод в позицию (60,k+2)

printf("\033[1;31m"); // и красный цвет

printf("End waiting\n");

for (k=9; k<24; k++) {

printf("\033[%02d;60H",k+1); // установить вывод в поз. (60,k+1)

printf("\033[1;31m"); // и красный цвет

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

printf("\n");

usleep(1000000);

}

}

 

void main()

{int k;

printf("\033[2J\n");//clrscr();

pthread_create(&tid1, NULL, (void*)procthread1, (void*)2);

pthread_create(&tid2, NULL, (void*)procthread2, (void*)3);

pthread_create(&tid3, NULL, (void*)procthread3, (void*)4);

for (k=0; k<24; k++) {

printf("\033[%02d;1H",k+1); //установить вывод в позицию (1,k+1) и белый цвет

printf("\033[1;37m");

printf("%c++\n",lbuk[k]);

if (k==9)

{pthread_cancel(tid2); // !!!

printf("\033[%02d;1H",k+1); // установить вывод в позицию (1,k+1)

printf("Kill Thread2\n");

}

usleep(1500000);

}

printf("\033[%02d;30H",24); // установить вывод в позицию (30,24)

pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_join(tid3,NULL);

printf("\033[0m"); // вернуть вывод на экран к стандартному цвету

}

Листинг 8.5.1. Программа с ожиданием завершения нитей для Unix

 

В программе с целью упрощения (ввиду того, что код возврата из процедуры не используется) значение кода завершения, возвращаемое с помощью функции pthread_join, описано как целочисленное (в видеint *rc;), а при вызове функции для второго аргумента используется преобразование типов в виде (void**)&rc. После вызова функции ожидания pthread_join третья нить, обратившаяся к этой функции, приостанавливается до тех пор, пока не завершится указанная в нем нить, т.е. первая нить. Завершение этой первой нити приводит к возобновлению действий приостановленной третьей нити. Для более легкого восприятия содержательных ситуаций программа процесса снабжена выдачей сообщений о приостановке и возобновлении нити.

В операционных системах Windows для ожидания завершения нити служит уже частично рассматривавшаяся универсальная функция ожидания WaitForSingleObject. Для ожидания завершения нити в качестве первого аргумента этой функции следует взять хэндл ожидаемой нити, а в качестве второго - значение предельного времени ожидания, в простейшем случае INFINITE. Каких-либо особенностей, отличающих ее от других форм ожидания, эта функция не имеет при использовании для ожидания нити.

Следующие фрагменты программы, представленные в листинге 8.5.2, демонстрируют простейшее ожидание завершения нити. Отсутствующие части программы совпадают с ее прототипом, записанным в листинге 8.2.2. Программа, восстановленная по указанным фрагментам, функционирует в Windows совершенно так, как было описано выше для программы в листинге 8.5.1, предназначенной для Unix.

 

#include <windows.h>

. . .

DWORD WINAPI procthread3(void *arg)

{int k, j;

COORD pos;

for (k=0; k<7; k++)

{// установка позиции вывода (60,k+1) и красный цвет вывода

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

. . .

Sleep(1000);

}

// установка позиции вывода (60,k+1) и красный цвет вывода

. . .

printf("Waiting End thread1");

LeaveCriticalSection(&csec);

WaitForSingleObject(hthread1, INFINITE);

// установка позиции вывода (60,k+1) и красный цвет вывода

. . .

printf("End waiting");

LeaveCriticalSection(&csec);

for (k=9; k<24; k++) {

// установка позиции вывода (60,k+1) и красный цвет вывода

. . .

for (j=0; j<(int)arg; j++) printf("%c",lbuk[k]);

LeaveCriticalSection(&csec);

Sleep(1000);

}

}

 

void main()

{DWORD threadid1, threadid2, threadid3;

. . .

for (k=0; k<24; k++) {

. . .

printf("%c++",lbuk[k]);

if (k==9)

{TerminateThread(hthread2, 0);

pos.X=1; pos.Y=k+1; SetConsoleCursorPosition(hstdout,pos);

printf("Kill Thread2");

}

LeaveCriticalSection(&csec);

Sleep(1500);

}

pos.X=30; pos.Y=24; SetConsoleCursorPosition(hstdout,pos);

. . .

}

Листинг 8.5.2. Программа с ожиданием завершения нитей для Windows

 

Данная программ принципиально не отличается от рассмотренных.

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

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