Параллельное программирование: Учебное пособие, страница 61


На приведенных рисунках сплошными стрелками с многоточиями отмечены интервалы выполнения демонстрируемых и прочих операторов в процессорных объектах, а пунктирными линиями – интервалы ожидания моментов завершения начатых операций.

Рисунок 3.3. Межпроцессорная связь при RPC.

3.5.2  Синхронизация

Для двух параллельно выполняющихся процессов, расположенных в разных процессорных объектах или процессорах, важно не только организовать связь и передать данные, выработанные или модифицированные одним процессом, другому процессу, который эти данные будет использовать и обрабатывать подобным же образом. Важно такой обмен согласовать во времени так, чтобы не нарушить целостности данных, последовательности их обработки двумя процессами и, по возможности, сохранить максимальную скорость выполнения общей задачи. Иными словами, необходимо обеспечить синхронную работу взаимодействующих процессов. На рисунке 3.4 показаны два альтернативных варианта реализации механизмов синхронизации.

Процесс, создающий данные, может использовать RPC, чтобы с помощью создаваемой им связи (по каналу) переместить данные в процессорный объект, потребляющий эти данные. Однако, к этому необходимо добавить такие условия, чтобы потребитель данных не начал их читать прежде, чем об этом сообщит производитель данных. То есть, необходим механизм блокировки возможного обращения к данным процесса-потребителя. Такие механизмы реализуются с помощью функций, имеющих в качестве аргументов семафоры (см. п. 2.4). CC++ использует и механизм однократного присваивания, и специальную переменную синхронизации (Рисунок 3.4б).

Рисунок 3.4.  Варианты механизмов синхронизации:

а) получатель блокируется, пока сообщение в канале;

б) получатель блокируется, пока переменная имеет значение.

Синхронизирующая переменная определяется модификатором типа sync со следующими свойствами:

1)  На момент инициализации переменная имеет специальное значение: "неопределенный".

2)  Это значение может быть назначено лишь однажды, например, в момент назначения констант в языках C ANSI и C++.

3)  Попытка читать неопределенную переменную вызывает процесс блокировки разделяемой процессами области памяти до тех пор, пока синхронизирующая переменная не получит своего значения.

В качестве переменной типа sync в С++ может выступать переменная любого регулярного типа, а в СС++  также и глобальный указатель, то есть возможны в программах следующие фрагменты записей:

sync int i;                          // i – целое типа sync

syncint *j;                        // j – указатель на целое типа sync

int *synck;                        // k – указатель типа sync на целое

syncint *syncl; // l – указатель типа sync на целое типа sync

Необходимость введения синхронизации можно показать на кодовом фрагменте, в котором предписывается параллельно выполнить удаленные вызовы процедур. RPCs, для примера, осуществляются к функциямread_len() и  write_len(42), определенным в тексте 3.5. Первая читает переменную length, а другая ее записывает.

Length *globallp;

int val;

par

{

val = lp->read_len();

lp->write_len(42);

}

Так как операции чтения и записи не синхронизированы, то значение val будет зависеть от того, что произошло раньше: чтение или запись? Если операция чтения началась на мгновение раньше операции записи, то val будет иметь неопределенное значение, так как в классе Length переменная length не инициализирована. Если порядок выполнения будет противоположным, то val получит записанное значение.

Неоднозначности можно избежать, изменив текст 3.5 так, чтобы сделать переменную length переменной типа sync. Для этого ее определение необходимо записать так: syncintlength;.

Порядок выполнения теперь не имеет значения: если процедура read_len начнет выполняться раньше, то, обнаружив неопределенность значения, операция чтения приостанавливается (блокируется) до тех пор, пока переменная length не получит значения от процедуры write_len.