Ожидание завершения нити
Другой формой управления нитями внутри программы являются функции ожидания завершения нити. В определенной степени эта функция аналогична функциям ожидания окончания процессов, но относится к уровню нитей. В конечном счете она предназначена для той же цели – согласования совместной работы нитей на основе самого глобального по смыслу действия – полного завершения работы отдельной выполняемой программной единицы.
В операционной системе 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
Данная программ принципиально не отличается от рассмотренных.
Считается, что разработка программ со многими нитями требует гораздо более высокой квалификации, чем обычное программирование. Отчасти это связано с инерционностью мышления программистов, сформировавшихся под влиянием идеи алгоритма. Напомним, что в алгоритме действия выполняются строго одно за другим, и никаких одновременных действий не допускается. При том, что поведение отдельной нити допускает естественное описание с помощью алгоритма, совокупное поведение программы со многими нитями оказывается гораздо сложнее, чем может описать отдельный алгоритм. Именно, в общем случае недетерминированное взаимодействие множества нитей и обусловливает трудность представления и мысленного моделирования поведения программы с многими нитями.
В то же время, многопоточные программы гораздо более естественно и полнее моделируют поведение объектов реального мира, где в действительности происходит множество взаимодействующих процессов и явлений, использующих общие элементы окружающей среды. Поэтому профессиональному программисту совершенно необходимо овладеть рассматриваемой разновидностью программирования.