Взаимное исключение на семафоре
Для реализации взаимного исключения, например, предотвращения возможности одновременного изменения двумя или более процессами общих данных, создается двоичный (с возможными значениями 0 и 1) семафор S. Начальное значение этого семафора - 1. Критические секции кода (секции, которые могут одновременно выполняться только одним процессом) обрамляются "скобками" P(S) (в начале секции) и V(S) (в конце секции). Процесс, входящий в критическую секцию, выполняет операцию P(S) и переводит семафор в 0. Если в критической секции уже находится другой процесс, то значение семафора уже 0, тогда второй процесс, желающий войти в критическую секцию, блокируется в своей P-операции до тех пор, пока процесс, находящийся в критической секции сейчас, не выйдет из нее, выполнив на выходе операцию V(S).
Синхронизация на семафоре
Большинство протоколов с активным ожиданием достаточно сложны. Кроме того, нет четкой грани между переменными для синхронизации и переменными для вычислений. Это усложняет разработку и использование протоколов с активным ожиданием.
Еще один недостаток активного ожидания – его неэффективность в большинстве многопоточных программ. Обычно количество процессов больше числа процессоров, за исключением синхронных параллельных программ, где на каждый процесс приходится по одному процессору, поэтому активное ожидание процесса становится эффективнее, если использовать процессор для выполнения другого процесса.
Синхронизация является основой параллельных программ, поэтому для разработки правильных протоколов синхронизации желательно иметь специальные средства, которые можно использовать для блокирования приостанавливаемых процессов. Первым таким средством синхронизации, не потерявшим актуальности и сегодня, стали семафоры. Они облегчают защиту критических секций и могут использоваться систематически для реализации планирования и сигнализации. По этой причине они включены во все известные автору библиотеки многопоточного и синхронного параллельного программирования. Кроме того, семафоры допускают различные способы реализации, как с помощью активного ожидания, описанного в предыдущей главе, так и с помощью ядра
Идея семафора в соответствии с названием взята из метода синхронизации движения поездов, принятого на железной дороге. Железнодорожный семафор – это "сигнальный флажок", показывающий, свободен путь впереди или занят другим поездом. По мере движения поезда семафоры устанавливаются и сбрасываются. Семафор остается установленным на время, достаточное, чтобы при необходимости остановить другой поезд. Таким образом, железнодорожные семафоры можно рассматривать как устройства, которые сигнализируют об условиях, чтобы обеспечить взаимоисключающее прохождение поездов по критическим участкам пути. Семафоры в параллельных программах аналогичны – они предоставляют базовый механизм сигнализации и используются для реализации взаимного исключения и условной синхронизации.
Синтаксис и семантика
Семафор – это особый тип разделяемой переменной, которая обрабатывается только двумя неделимыми операциями Р и V. Семафор можно считать экземпляром класса семафор, операции Р и v – методами этого класса с дополнительным атрибутом, определяющим их неделимость.
Значение семафора является неотрицательным целым числом. Операция V используется для сигнализации о том, что событие произошло, поэтому она увеличивает значение семафора. Операция р приостанавливает процесс до момента, когда произойдет некоторое событие, поэтому она, дождавшись, когда значение семафора станет положительным, уменьшает его. Сила семафоров обусловлена тем, что выполнение операции Р может быть приостановлено.
Семафор объявляется так: sem s ;
По умолчанию начальным значением является 0, но семафор можно инициализировать любым положительным значением, например:
sem lock = 1; Массивы семафоров можно объявлять и при необходимости инициализировать обычным образом:
sem forks[5] = ([5] 1);
Если бы в этой декларации не было инициализации, то начальным значением каждого семафора в массиве forks был 0.
После объявления и инициализации семафор можно обрабатывать только с помощью операций р и V. Каждая из них является неделимым действием с одним аргументом. Пусть s – семафор. Тогда операции P (s) и V (s) определяются следующим образом.
P(s): < await (s>0) s=s-l; >
V(s) : < s = s + 1; >
Операция V увеличивает значение s на единицу неделимым образом. Операция Р уменьшает значение s, но, чтобы после вычитания значение s не стало отрицательным, она сначала ожидает, пока s не станет положительным.
Приостановка выполнения и вычитание в операции р являются единым неделимым действием. Предположим, что s – семафор с текущим значением 1. Если два процесса пытаются одновременно выполнить операцию Р (s), то это удастся сделать только одному из них. Но если один процесс пытается выполнить операцию Р (s), а другой – V (s), то обе операции будут успешно выполнены в непредсказуемом порядке, а конечным значением семафора s станет 1.
Обычный семафор может принимать любые неотрицательные значения, двоичный семафор – только значения 1 или 0. Это значит, что операция V для двоичного семафора может быть выполнена, только когда его значение 0. (Операцию V для двоичного семафора можно определить как ожидание, пока значение семафора станет меньше 1, и затем его увеличение на 1.)
Поскольку операции с семафором определяются в терминах операторов await, их формальная семантика следует непосредственно из применения правила оператора await. Правила вывода для операций Р и V получаются непосредственно из правила оператора await, примененного к конкретным операторам в определении Р и V.
Свойства справедливости для операций с семафорами тоже следуют из их определения с помощью оператора await. Если условие s > 0 становится и далее остается истинным, выполнение операции Р (s) завершится при справедливой в слабом смысле стратегии планирования. Если условие s > 0 становится истинным бесконечно часто, то выполнение операции Р (s) завершится при справедливой в сильном смысле стратегии планирования. Операция V для обычного семафора является безусловным неделимым действием, поэтому она завершится, если стратегия планирования безусловно справедлива.
При реализации семафоров обычно обеспечивается, что, если процессы приостанавливаются, выполняя операции р, они возобновляются в том же порядке, в котором были приостановлены. Следовательно, процесс, ожидающий выполнения операции Р, сможет в конце концов продолжить работу, если другие процессы выполнят соответствующее число операций V.
