Управление процессами. Синхронизация процессов и потоков

Download Report

Transcript Управление процессами. Синхронизация процессов и потоков

Управление
процессами
Синхронизация процессов и
потоков
Синхронизация

Механизм операционной системы,
обеспечивающий согласованное во
времени выполнение нескольких
процессов и/или потоков
Типы процессов



Синхронные – действия процессов во времени
строго согласованы; действия любого
синхронного потока строго обусловлены
действиями других синхронных с ним потоков
Несинхронные – процессы, выполняющиеся
абсолютно независимо друг от друга
Асинхронные – независимые процессы, действия
которых необходимо синхронизировать на
некоторых этапах их выполнения
Разделяемые ресурсы и
критические секции


Разделяемый (критический) ресурс – это такой
ресурс системы, который может использоваться
несколькими процессами или потоками, при чем
процессы и потоки могут пытаться использовать
этот ресурс параллельно
Критическая секция – область программного кода
процесса или потока, при выполнении которой
происходит обращение к разделяемому
(критическому) ресурсу
Пример несинхронного обращения к
критическому ресурсу




Переменная х –
критический ресурс
Процессы Р1 и Р2
выполняются строго
параллельно и не
tmp=8
синхронно
Каждый процесс должен
tmp=9
добавить к значению
переменной х единицу
В результате работы
x=9
процессов значение х
должно быть увеличено на
2, но по факту оно
увеличится только на 1
х=8
Р1
Р2
tmp = x
tmp = x
tmp=8
tmp++
tmp++
tmp=9
x = tmp
x = tmp
x=9
х = 10
x=9
Взаимоисключение (mutual
exclusion)

Механизм операционной системы,
позволяющий реализовать синхронный
вход процессов (потоков) в свои
критические секции





Переменная х –
критический ресурс
Процессы Р1 и Р2
выполняются асинхронно
Перед обращением к
критическому ресурсу
процессы выполняют
примитив
взаимоисключения
start_exclusion
По завершению
критической секции
выполняется примитив
end_exclusion
Каждый процесс должен
добавить к значению
переменной х единицу
Р1
Р2
start_exclusion
tmp=8
tmp = x
tmp=9
tmp++
x=9
x = tmp
end_exclusion
процесс Р2 ожидает
завершения
критической секции
процесса Р1
Пример обращения к критическому
ресурсу с использованием механизма
взаимоисключения
х=8
start_exclusion
tmp = x
tmp=9
tmp++
tmp=10
x = tmp
x=10
end_exclusion
х = 10
Правила по работе с
критическими секциями







Никакие два процесса не могут одновременно войти в свои
критические секции
В каждый момент времени только один процесс может находится в
своей критической секции
Никакой процесс не может находиться в своей критической секции
бесконечно
Никакой процесс не должен бесконечно ждать входа в свою
критическую секцию
Операции входа в критическую секцию должны выполняться в ОС
как атомарные, чтобы исключить возможность параллельного
выполнения этих операций
Разработка приложений с критическими секция не должна вестись
с учетом особенностей дисциплины планирования, операционной
системы и/или вычислительной системы
Вся ответственность за корректную/некорректную работу
приложений с взаимоисключениями лежит на программисте
Примитивы и механизмы
синхронизации






Примитивы взаимоисключения (объект
CRITICAL_SECTION в ОС Windows)
Примитивы ожидания
Семафоры
Mutex
События и условные (сигнальные) переменные
Средства языков параллельного
программирования (мониторы и механизм
рандеву языка ADA)
Объект CRITICAL_SECTION в
Windows




Реализован в ОС Windows как примитив для
реализации взаимоисключения потоков
Определяется в рамках процесса, а не ядра
операционной системы
Может быть использован только для
синхронизации потоков
Т.к. ядро Windows не знает о существовании
этого объекта, он не может быть использован для
синхронизации процессов или потоков,
запущенных в рамках разных процессов
Использование
CRITICAL_SECTION

Определить переменную типа CRITICAL_SECTION как глобальную
переменную процесса


Перед запуском асинхронных потоков инициализировать
критическую секцию


DeleteCriticalSection(&cs);
В потоке, перед входом в критическую секцию, проверить,
свободна ли она, и заблокировать вход в критическую секцию для
других потоков


InitializeCriticalSection(&cs);
После завершения асинхронных потоков удалить критическую
секцию


CRITICAL_SECTION cs;
EnterCriticalSection(&cs);
После завершения критической секции разрешить вход другим
потокам

LeveCriticalSection(&cs);
Пример использования
критических секций




Функция main() запускает n потоков,
каждый из которых должен увеличить
значение глобальной переменной x на 1
Для синхронизации потоков используется
глобальный объект CRITICAL_SECTION
Каждый из n потоков выполняет функцию
thread_func()
Полный листинг находится в файле
critical_section.cpp
Примитивы ожидания в ОС
Windows

Ожидание завершения одного потока


WaitForSingleObject(дескриптор_потока
, время_ожидания)
Ожидание завершения нескольких потоков

WaitForMultipleObjects(
количество_потоков,
массив_дескрипторов,дожидаться_всех
(true/false), время_ожидания)
Объекты Mutex (mutual
exclusion)



Объект ядра операционной системы,
обеспечивающий взаимоисключения потоков
В ОС Linux mutex не является объектом ядра ОС,
что не позволяет ему синхронизировать работу
процессов и потоков в разных процессах
Принимает два значения



Открыт – поток может войти в свою критическую
секцию
Закрыт – поток не может войти в критическую секцию
Если mutex закрыт, то поток пытающийся войти в
критическую секцию блокируется
Использование mutex в
Windows

Создание mutex



Открытие mutex


OpenMutex(параметры_безопасности,
собственность_потока, имя)
Ожидание и захват mutex


CreateMutex(параметры_безопасности,
собственность_потока, имя)
Поименованный mutex может использоваться для
синхронизации процессов
WaitForSingleObject(дескриптор_mutex,
время_ожидания)
Освобождение mutex

ReleaseMutex(дескриптор_mutex)
Использование mutex в Linux





Mutex в Linux – это объект для синхронизации потоков в
рамках одного процесса
Описание mutex
 pthread_mutex_t mutex;
Инициализация mutex
 pthread_mutex_init (&mutex, NULL);
Перед входом в критическую секцию поток должен
попытаться захватить mutex. Если это не удается, то поток
блокируется до освобождения mutex
 pthread_mutex_lock(&mutex);
При выходе из критической секции поток должен освободить
mutex
 pthread_mutex_unlock(&mutex);
Семафоры




Семафоры – это объекты синхронизации, которые могут
находиться в двух состояниях – открыт и закрыт.
Бинарные семафоры только открывают или закрывают
вход в критическую секцию. По своему поведению похожи на
mutex.
Считающие семафоры позволяют определить, сколько
процессов могут одновременно войти в свою критическую
секцию
 Если счетчик = 0, то вход в критическую секцию не возможен
 Если счетчик > 0, то процесс может войти в свою
критическую секцию
В отличии от mutex, семафоры реализуют активное
ожидание – процесс на семафоре не блокируется, а
продолжает проверять, не открылся ли семафор
Операции с семафорами
Операция понижения
Операция повышения
Используется процессами при
входе в критическую секцию
Используется процессами при
выходе из критической
секции
If (semaphore > 0)
then dec(semaphore)
else while (semaphore = 0) do;
Inc(semaphore)
Использование семафоров в
Windows



Создание семафора
 CreateSemaphore(параметры_безопасности,
начальное_значение, максимальное_значение, имя)
При входе в критическую секцию поток должен захватить
семафор или войти в стадию активного ожидания (операция
понижения)
 WaitForSingleObject(дескриптор_семафора, время
ожидания)
При выходе из критической секции поток должен повысить
значение счетчика семафора
 ReleaseSemaphore(дескриптор_семафора,
добавляемое_значение,
место_для_предыдущего_значения)
Использование семафоров
для потоков в Linux





Для работы с семафорами потоков необходимо подключить
модуль semaphore.h
 #include <semaphore.h>
Описание переменной семафора
 sem_t semaphore;
Инициализация семафора
 sem_init(&semaphore,0,начальное_значение);
Перед входом в критическую секцию необходимо дождаться
освобождения семафора и понизить значение его счетчика
 sem_wait(&semaphore);
По окончании критической секции необходимо повысить
значение счетчика семафора
 sem_post(&semaphore);
Использование семафоров
процессов в UNIX/Linux

Для работы с семафорами процессов необходимо
подключить следующие модули




Описать структуру, используемую для инициализации
семафоров


#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
union semun
 { int val;

struct semid_de * buf;

unsigned short int * array;

struct seminfo * __buf;
 };
Описать целую переменную для хранения идентификатора
группы семафоров

int sem_id;
Использование семафоров
процессов в UNIX/Linux


Создать группу семафоров
 sem_id = semget(ключ_группы,
количество_семафоров, флаги_создания);
 Если указан ключ существующей группы, то будет возвращен
ее идентификатор. В этом случае второй параметр может
быть равен нулю.
Инициализировать семафоры созданной группы (задать
начальные значения)
 Подготовить структуру типа union semun





unsigned short int
start_values[количество_семафоров];
Каждому элементу массива start_values присвоить
начальное значение конкретного семафора
union semun sem_values;
sem_values.array = start_values;
semctl(sem_id,0,SETALL,sem_values);
Использование семафоров
процессов в UNIX/Linux

Изменение значений семафоров

Создать массив элементов типа struct sembuf


struct sembuf oper[количество_семафоров];
Заполнить каждый элемент массива oper значениями операций:


oper[номер_семафора].sem_num = номер_семафора;
oper[номер_семафора].sem_op = целое_значение;





Если целое_значение < 0, то значение семафора должно
уменьшиться (операция понижения). Если после предполагаемого
уменьшения значение семафора станет меньше нуля, то процесс
блокируется и ждет, пока уменьшение не станет приводить к
получению отрицательного результата
Если целое_значение = 0, то семафор не меняется
Если целое_значеие > 0, то семафор увеличивается (операция
повышения)
oper[номер_семафора].sem_flg = 0;
Выполнить функцию semop
 semop(sem_id, oper, количество_семафоров);
Использование семафоров
процессов в UNIX/Linux

В конце работы с семафорами их
необходимо уничтожить, т.к. они являются
объектами ядра системы и могут
существовать независимо от того,
существует создавший их процесс или нет

semctl(sem_id, количество_семафоров,
IPC_RMID, NULL);
Задача «Читатели-писатели»





В системе имеется n писателей, которые
производят некоторые данные
В системе имеется m читателей, которые
потребляют произведенные данные
Обмен данными между читателями и писателями
ведется через общий буфер
Писатели могут писать в буфер, только если в
буфере есть пустые ячейки
Читатели могут читать из буфера, только если в
буфере есть какие-либо данные. Прочитав
данные из буфера, читатель освобождает одну
ячейку
Принципиальная реализация
задачи «Читатели – писатели»





Писатель
Дождаться освобождения
хотя бы одной ячейки
буфера
Захватить буфер
Записать данные в
первую свободную ячейку
Освободить буфер
Сообщить о том, что в
буфере появилась новая
информация





Читатель
Дождаться появления в
буфере хотя бы одной
записанной ячейки
Захватить буфер
Прочитать данные из
буфера и освободить
ячейку
Освободить буфер
Сообщить о появлении в
буфере свободной ячейки
Средства синхронизации в
задаче «Читатели – писатели»



Mutex (или простой бинарный семафор) для блокирования
одновременного обращения нескольких процессов (потоков)
к буферу
Семафор empty – считающий семафор, максимальное
значение которого равно количеству ячеек в буфере. Если
значение семафора free = 0, то в буфере нет свободных
ячеек, и все писатели должны быть заблокированы.
Создается с начальным значением равным n.
Семафор full – считающий семафор, максимальное значение
которого равно количеству ячеек в буфере. Если значение
семафора full = 0, то в буфере нет данных, и все читатели
должны быть заблокированы. Создается с начальным
значением равным 0.
События в Windows




События – средства условной синхронизации потоков и
процессов
Для создания события необходимо выполнить функцию
 CreateEvent(атрибуты_безопасности,
режим_переключения, начальное_значение,имя);
Для сообщения о том, что событие произошло, необходимо
выполнить функцию
 SetEvent(дескриптор_события);
Поток, ожидающий наступления события, должен выполнить
функцию
 WaitForSingleObject(дескриптор_события,
время_ожидания);
Режимы переключения
события



Автоматический
Второй параметр функции
CreateEvent равен false
Событие переходит в
состояние «не произошло»
сразу после того, как хотя бы
один поток дождался этого
события на функции
WaitForSingleObject
Сразу после того, как один
поток прошел ожидание
события, другой поток так же
может выйти из состояния
ожидания


Ручной
Второй параметр функции
CreateEvent равен true
Чтобы перевести событие в
состояние «не произошло»
поток должен выполнить
функцию
ResetEvent(дескриптор)

Только один поток может войти
в критическую секцию после
завершения ожидания
наступления события на
функции
WaitForSingleObject
Задача «Производитель –
потребитель»




Производитель – поток, который генерирует
данные
Потребитель – поток, которые использует данные
Производитель может сгенерировать новые
данные только после того, как потребитель
обработал предыдущую порцию
Потребитель может обрабатывать каждую
порцию данных только один раз, поэтому
вынужден ожидать, когда производитель
сгенерирует следующий набор данных
Принципиальная реализация задачи
«Производитель – потребитель»





Производитель
Дождаться момента, когда
потребитель обработает
данные
Захватить буфер
Сгенерировать новые
данные
Освободить данные
Сообщить о том, что
новые данные
сгенерированы





Потребитель
Дождаться момента, когда
будут сгенерированы
новые данные
Захватить буфер
Обработать данные
Освободить буфер
Сообщить о том, что
данные обработаны, и
потребитель ждет новых
данных
Средства синхронизации в задаче
«Производитель – потребитель»



Mutex для блокирования параллельного доступа к буферу
Событие e_generated – сообщает о том, что данные
сгенерированы. Событие создается, как не произошедшее,
что не позволит потребителю сразу захватить буфер.
Событие должно иметь ручное переключение, чтобы
несколько потребителей не смогли одновременно обратиться
к данным.
Событие e_handled – сообщает о том, что данные
обработаны. Событие создается, как уже произошедшее,
чтобы производитель имел возможность сразу сгеренировать
данные. Событие должно иметь ручное управление, чтобы
несколько производителей не смогли одновременно
обращаться к данным
Условные (сигнальные)
переменные в Linux



Условная (сигнальная) переменная – это
средство условной синхронизации потоков
в Linux
Условная переменная сообщает потоку о
том, что произошло событие, которого
поток ожидал
Условная переменная всегда связана с
некоторым mutex, т.к. сама является
критической переменной
Использование условных
(сигнальных) переменных




Описать переменную сигнальной переменной
 pthread_cond_t cond;
Инициализировать сигнальную переменную
 pthread_cond_init(&cond,NULL);
Поток ожидает наступления события при помощи функции
 pthread_cond_wait(&cond,указатель_на_mutex);
 Перед проверкой наступления события mutex, с которым
связана переменная, уже должен быть захвачен функцией
pthread_mutex_lock(указатель_на_mutex)
Сигнализировать об изменении переменной поток может
функцией
 pthread_cond_signal(&cond);
 При этом разблокируется один из потоков, ожидающих
наступления события
Механизм рандеву




Рандеву – механизм синхронизации задач,
реализованный в языке программирования ADA
Используется для жесткой синхронизации двух
асинхронных задач
Вызывающая задача – это процесс, которому
требуются данные от другого процесса
Вызываемая (обслуживающая) задача – это
процесс, который формирует данные для
вызывающей задачи
Правила рандеву



Вызывающая задача
Процесс выполняется
асинхронно
Перед входом в рандеву
должен быть осуществлен
вызов обслуживающей
задачи
Вызывающая задача
должна ожидать, пока
обслуживающая задача
не достигнет своего блока
приема (accept)



Обслуживающая задача
Процесс выполняется
асинхронно
Вход в рандеву возможен
только после того, как
вызывающая задача
выполнить вызов
обслуживающей
Рандеву для
обслуживающей задачи
начинается с блока
приема (accept)
Схема рандеву
Вызывающая задача
Обслуживающая задача
Собственные действия
вызывающей задачи
Собственные действия
обслуживающей задачи
Блок вызова
(call)
Блок приема
(accept)
Рандеву
Собственные действия
вызывающей задачи
Собственные действия
обслуживающей задачи
Использование рандеву – получение
параметров, отложенным вычислением





Вызывающая задача – процесс, который
выполняет обработку случайным образом
сгенерированного массива
Обслуживающая задача – процесс,
генерирующий массив по запросу
Массив – критический ресурс, доступ к которому
регулируется mutex
Когда вызывающая задача достигает блока
вызова, она генерирует событие call
Когда обслуживающая задача достигает блока
приема она генерирует событие accept
Мониторы




Монитор – средство синхронизации и
коммуникации задач, реализованное в языке
ADA
Монитор содержит разделяемые данные, к
которым обращаются процессы
Процессы, обращающиеся к данным монитора
ставятся в очередь
При освобождении монитора одним процессом,
он будет захвачен процессом, который стоит в
начале очереди ожидания
Примеры программ
critical_section.cpp
Использование критических секций для
синхронизации потоков в Windows
mutex_win.cpp
Использование mutex в Windows
bin_semaphore_win.cpp
Использование бинарных семафоров в
Windows
rw_win.cpp
Реализация задачи «читатели – писатели» для
Windows (mutex и считающие семафоры)
pc_win.cpp
Реализация задачи «производитель –
потребитель» для Windows (mutex и события)
rendezvous_win.cpp
Эмуляция механизма рандеву (mutex и
события)
Примеры программ
mutex_linux.cpp
Использование mutex в Linux
bin_sem_linux.cpp
Использование семафоров для
потоков в Linux
bin_sem_unix.cpp
Использование бинарных семафоров в
стиле UNIX
rw_linux.cpp
Реализация задачи «читательписатель» для Linux (mutex и
считающие семафоры для потоков)