Участки программы, где происходит работа с общей памятью, часто следует рассматривать как критические секции и защищать семафорами
Общая память
Сообщения
Сообщения также посылаются процессу системой или другим процессом, однако отличаются от сигналов в двух отношениях.
Во-первых, сообщения не прерывают работу процесса-получателя. Вместо этого они становятся в очередь сообщений. Процесс должен сам вызвать функцию приема сообщения. Если очередь пуста, эта функция блокирует процесс до получения какого-нибудь сообщения.
Во-вторых, с сообщением, в отличие от сигнала, может быть связана информация, передаваемая получателю. Таким образом, сообщения – это средство не только синхронизации, но и обмена данными между процессами.
Поговорим теперь еще об обмене данными. Самым простым и естественным способом такого обмена представляется возможность совместного доступа двух или более процессов к общей области памяти.
Но поскольку обычно ОС стремится, наоборот, надежно разделить память разных процессов, то для выделения обшей памяти нужны специальные системные средства.
Общая память служит только средством обмена данными, но никак не решает проблем синхронизации.
Другое часто используемое средство обмена данными – программный канал (pipe; иногда переводится как «трубопровод»).
В этом случае для выполнения обмена используются не команды чтения/записи в память, а функции чтения/записи в файл.
Программный канал «притворяется файлом», для работы с ним используются те же операции, что для последовательного доступа к файлу: открытие, чтение, запись, закрытие.
Однако источником читаемых данных служит не файл на диске, а процесс, выполняющий запись «в другой конец трубы».
Данные, записанные одним процессом, но пока не прочитанные другим, хранятся в системном буфере.
Если же процесс пытается прочесть данные, которые пока не записаны другим процессом, то процесс-читатель блокируется до получения данных
При необходимости использовать один и тот же ресурс параллельные процессы вступают в конфликт (конкурируют) друг с другом.
Каждый из процессов не подозревает о наличие остальных и не подвергается никакому воздействию с их стороны.
Отсюда следует, что каждый процесс не должен изменять состояние любого ресурса, с которым он работает.
Примерами таких ресурсов могут быть устройства ввода-вывода, память, процессорное время, часы.
Между конкурирующими процессами не происходит никакого обмена информацией.
Однако выполнение одного процесса может повлиять на поведение конкурирующего процесса. Это может, например, выразиться в замедлении работы одного процесса, если ОС выделит ресурс другому процессу, поскольку первый процесс будет ждать завершения работы с этим ресурсом.
В предельном случае блокированный процесс может никогда не получить доступа к нужному ресурсу и, следовательно, никогда не сможет завершиться.
В случае конкурирующих процессов (потоков) возможно возникновение трех проблем. Первая из них - необходимость взаимных исключений(mutual exclusion).
Предположим, что два или большее количество процессов требуют доступ к одному неразделяемому ресурсу, как принтер (рис.).
О таком ресурсе будем говорить как о критическом ресурсе, а о части программы, которая его использует, - как о критическом разделе (секции) (critical section) программы.
Крайне важно, чтобы в критической ситуации в любой момент могла находиться только одна программа.
Например, во время печати файла требуется, чтобы отдельный процесс имел полный контроль над принтером, иначе на бумаге можно получить чередование строк двух файлов.
Осуществление взаимных исключений создает две дополнительные проблемы. Одна из них - взаимоблокировки (deadlock) или тупики.
Очень удобно моделировать условия возникновения тупиков, используя направленные графы. Графы имеют 2 вида узлов: процессы-кружочки и ресурсы-квадратики. Ребро, направленное от квадрата (ресурса) к кружку (процессу), означает, что ресурс был запрошен, получен и используется.
Рассмотрим, например, два процесса - Р1 и Р2 и два ресурса - R1 и R2. Предположим, что каждому процессу для выполнения части своих функций требуется доступ к общим ресурсам R1 и R2.
Тогда возможно возникновение следующей ситуации: ОС выделяет первоначально ресурс R1 процессу Р2, а ресурс R2 - процессу Р1.
Но каждому из них необходим для работы и другой ресурс, поэтому каждый из них запрашивает тот ресурс, который у него отсутствует.
В результате каждый процесс ожидает получения одного из двух ресурсов. При этом ни один из них не освобождает уже имеющийся ресурс, ожидая получения второго ресурса для выполнения функций, требующих наличия двух ресурсов. В результате процессы оказываются взаимно заблокированными
Существует еще одна проблема у конкурирующих процессов - голодание.
Предположим, что имеются 3 процесса (P1, P2, РЗ), каждому из которых периодически требуется доступ к ресурсам R.
Представим ситуацию, в которой Р1 обладает ресурсом, а Р2 и РЗ приостановлены в ожидании освобождения ресурса R. После выхода Р1 из критического раздела доступ к ресурсу будет получен одним из процессов Р2 или РЗ
Пусть ОС предоставила доступ к ресурсу процессу РЗ. Пока он работает с ресурсом, доступ к ресурсу вновь требуется процессу Р1. В результате по освобождении ресурса R процессом РЗ может оказаться, что ОС вновь предоставит доступ к ресурсу процессу Р1. Тем временем процессу РЗ вновь требуется доступ к ресурсу R. Таким образом, теоретически возможна ситуация, в которой процесс Р2 никогда не получит доступа к требуемому ему ресурсу, несмотря на то что никакой взаимной блокировки в данном случае нет.