
- •Операционные системы для программиста
- •Введение
- •1. Основные понятия
- •1.1. Понятие операционной системы
- •1.2. Системные соглашения для доступа к функциям ос
- •1.3. Особенности разработки программ в базовых ос
- •1.4. Командный интерфейс пользователя в ос
- •1.5. Информация об ошибках системной функции
- •2. Программный доступ к файловой системе
- •2.1. Понятия дескрипторов, идентификаторов и хэндлов
- •2.2. Ввод и вывод в стандартные файлы.
- •2.3. Базовые средства использования файлов
- •2.4. Многопользовательская блокировка файлов
- •2.5. Установка произвольной позиции в файле
- •3. Принципы построения ос
- •3.1. Модульная структура построения ос
- •3.2. Использование прерываний в ос
- •3.3. Управление системными ресурсами
- •3.4 Строение ядра операционной системы
- •3.5. Структура операционной системы типа Windows nt
- •4. Многофункциональный консольный вывод
- •4.1. Функции управления курсором
- •4.2. Многократный вывод символов и атрибутов
- •4.3. Вывод в произвольную позицию экрана
- •4.4. Ввод данных, размещенных предварительно на экране
- •5. Системные функции ввода для консольных устройств
- •5.1. Системные функции ввода текстовых строк
- •5.2. Событийно-управляемый ввод
- •5.3. Системные функции ввода с клавиатуры
- •5.4. Опрос ввода с клавиатуры в программе
- •5.5. Системные функции мыши для текстового режима
- •6. Файловые системы
- •6.1. Структуры файловых систем для пользователя
- •6.2. Методы распределения внешней памяти
- •6.3. Принципы построения файловых систем типа fat
- •6.4. Современные модификации файловой системы fat
- •6.5. Особенности построения файловой системы hpfs
- •6.6. Принципы построения файловой системы ntfs
- •6.7. Особенности строения файловых систем для Unix
- •6.8. Программный опрос файловой системы
- •7. Обеспечение множественности процессов
- •7.1. Основные понятия теории вычислительных процессов
- •7.2. Программное порождение процессов
- •7.3. Уничтожение процессов
- •7.4. Ожидание завершения процессов
- •8. Многопоточное функционирование ос
- •8.1. Понятие нити и связь Хе с процессом
- •8.2. Создание нитей (thread) в программе
- •8.3. Уничтожение нитей
- •8.4. Приостановка и повторный запуск нити
- •8.5. Ожидание завершения нити
- •9. Средства взаимодействия программных единиц
- •9.1. Абстрактные критические секции
- •9.2. Абстрактные семафоры
- •9.3. Семафоры взаимоисключения
- •9.4. Семафоры событий
- •9.5. Средства группового ожидания
- •9.6. Программные критические секции
- •9.7. Программные семафоры с внутренним счетчиком
- •10. Управление памятью
- •10.1. Виртуальная память
- •10.2. ЏодкРчка страниц для реализациШ виртуальной памяти
- •10.3. Системные функции распределения памяти
- •10.4. Совместное использование памяти
- •10.5. Отображение файлов в оперативную память
- •10.6. Динамически распределяемая память
- •11. Средства коммуникации процессов
- •11.1. Неименованные коммуникационные каналы Unix
- •11.2. Переназначение хэндлов для доступа к каналу
- •11.3. Неименованные каналы в Windows
- •11.4. Именованные каналы в Windows nt
- •11.5. Именованные каналы в Unix
- •12. Взаимодействие пользователя с ос
- •12.1. Интерфейсы операционных систем
- •12.2. Командные и операционные оболочки (shells)
- •12.3. Основные команды базовых операционных систем
- •12.4. Групповое выполнение и фоновый запуск команд
- •12.5. Стандартный ввод-вывод и конвейеры командной строки
- •12.6. Командные файлы и сценарии
- •Библиографический список
8.5. Ожидание завершения нити
Другой формой управления нитями внутри программы являются функции ожидания завершения нити. В определенной степени эта функция аналогична функциям ожидания окончания процессов, но относится к уровню нитей. В конечном счете она предназначена для той же цели – согласования совместной работы нитей на основе самого глобального по смыслу действия – полного завершения работы отдельной выполняемой программной единицы.
В операционной системе 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
Данная программ принципиально не отличается от рассмотренных.
Считается, что разработка программ со многими нитями требует гораздо более высокой квалификации, чем обычное программирование. Отчасти это связано с инерционностью мышления программистов, сформировавшихся под влиянием идеи алгоритма. Напомним, что в алгоритме действия выполняются строго одно за другим, и никаких одновременных действий не допускается. При том, что поведение отдельной нити допускает естественное описание с помощью алгоритма, совокупное поведение программы со многими нитями оказывается гораздо сложнее, чем может описать отдельный алгоритм. Именно, в общем случае недетерминированное взаимодействие множества нитей и обусловливает трудность представления и мысленного моделирования поведения программы с многими нитями.
В то же время, многопоточные программы гораздо более естественно и полнее моделируют поведение объектов реального мира, где в действительности происходит множество взаимодействующих процессов и явлений, использующих общие элементы окружающей среды. Поэтому профессиональному программисту совершенно необходимо овладеть рассматриваемой разновидностью программирования.
Упражнения
1. Разработать программу для Windows с тремя нитями, дополнительными к главной и создаваемыми системными функциями CreateThread. Каждая из нитей должна использовать общие для всех нитей данные, заданные 23 первыми буквами латинского алфавита. Каждая из этих нитей на своем k-м шаге выводит со своей случайной задержкой на место "своего" столбца экрана k-ю букву из указанного массива латинских букв, причем с числом повторений, равным условному номеру нити. На 6-м шаге главной нити она приостанавливает первую из дополнительных нитей, а на 13-м шаге задает возобновление выполнения этой нити. Вторая дополнительная нить на 11-м своем шаге отдает приказ на уничтожение третьей дополнительной нити. Все управляющие указания должны отображаться сообщениями без прокрутки экрана (в фиксированные позиции экрана).