Сравнение блокирующих и неблокирующих вызовов


Описанные ранее вызовы являются блокирующими вызовами (иногда их называют синхронными вызовами).
Когда процесс вызывает процедуру send, он указывает получателя и отправляемый ему буфер. Пока сообщение отправляется, отправляющий процесс блокируется (то есть приостанавливается). Команда, следующая за вызовом процедуры send, не выполняется до тех пор, пока не будет отправлено все сообщение (рис. 8.19, а). Точно так же после вызова процедуры receive управление процессу не возвращается до тех пор, пока сообщение не будет получено и помещено в буфер сообщений, указанный в параметрах. Процесс находится в приостановленном состоянии после вызова receive до тех пор, пока не поступит сообщение, даже если на это уйдет несколько часов. На некоторых системах процесс, принимающий сообщение, может указать, от кого он хочет его получить, в таком случае он остается заблокированным, пока сообщение не придет от этого отправителя.


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

Но преимущества в росте производительности, предлагаемые неблокирующими процедурами, скрадываются серьезным недостатком: отправитель не может изменять содержимое буфера сообщений до тех пор, пока сообщение не будет отправлено. Последствия переписывания процессом сообщения в ходе его отправки настолько ужасны, что о них лучше не упоминать. Хуже того, процесс, отправляющий сообщение, ничего не знает о том, когда оно будет отправлено, поэтому он не в курсе, когда можно будет повторно воспользоваться буфером безо всякого ущерба. Так может дойти до того, что он будет избегать пользоваться им до бесконечности.

Можно воспользоваться тремя выходами из этой ситуации. Первое решение (рис. 8.19, б) заключается в том, чтобы ядро копировало сообщение в свой внутренний буфер, а потом позволяло процессу продолжить работу. С точки зрения отправителя эта схема ничем не отличается от схемы с блокирующим вызовом: как только управление будет получено обратно, процесс волен использовать буфер по своему усмотрению. Разумеется, на тот момент сообщение еще не будет отправлено, но отправителю это уже не помешает. Недостаток этого метода в том, что каждое исходящее сообщение должно копироваться из пространства пользователя в пространство ядра. При использовании многих сетевых интерфейсов сообщение все равно чуть позже должно быть скопировано в аппаратный буфер передачи, поэтому первое копирование, по сути, будет лишним. Дополнительное копирование существенно снижает производительность системы.

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

Третье решение заключается в том, чтобы копировать буфер при записи и помечать его доступным только для чтения до тех пор, пока сообщение не будет отправлено. Если буфер повторно используется еще до отправки сообщения, то создается его копия. Проблема этого решения в том, что пока буфер не будет изолирован на собственной странице, запись соседних переменных также будет вызывать копирование.

Кроме этого, потребуется дополнительное администрирование, потому что отправка сообщения теперь всецело влияет на статус чтения-записи страницы. И наконец, рано или поздно страницу, скорее всего, придется записывать опять, инициируя создание копии, которая уже никогда не понадобится.

Таким образом, у отправителя есть следующий выбор:

♦ Использовать блокирующую процедуру send (центральный процессор будет простаивать в течение всей передачи сообщения).

♦ Использовать неблокирующую процедуру send с копированием (время центрального процессора тратится впустую на дополнительное копирование).

♦ Использовать неблокирующую процедуру send с прерыванием (при этом усложняется программирование).

♦ Использовать копирование при записи (в конечном счете может понадобиться дополнительная копия).

При обычных условиях первый вариант наиболее удобен, особенно если доступны несколько потоков. В этом случае, пока один поток заблокирован при попытке отправки сообщения, другие потоки могут продолжить свою работу. Кроме того, не нужно будет управлять никакими буферами ядра. И еще: при сравнении рис. 8.19, а и рис. 8.19, б можно заметить, что если не нужно делать копию, то сообщение появится на выходе быстрее.

Чтобы не было недоразумений, следует заметить, что некоторые авторы используют различные критерии отличия синхронных примитивов от несинхронных. Есть мнение, что вызов является синхронным, только если отправитель заблокирован до тех пор, пока не будет получено сообщение и обратно не будет отправлено подтверждение (Andrews, 1991). А в мире коммуникаций в реальном масштабе времени синхронизация имеет несколько иной смысл, что, к сожалению, приводит к путанице.

Процедура receive так же, как и процедура send, может быть блокирующей и неблокирующей. Блокирующий вызов просто приостанавливает вызывающий процесс до тех пор, пока не поступит сообщение. Если есть возможность использования нескольких потоков, то такой подход трудностей не вызывает. В отличие от него вызов неблокирующей процедуры receive просто сообщает ядру, где находится буфер, и управление практически тут же возвращается. Чтобы дать сигнал о поступлении сообщения, можно использовать прерывание. Однако прерывания трудны в программировании и довольно медленно работают, поэтому, может быть, лучше отдать предпочтение периодическому опросу получателя о наличии входящего сообщения, осуществляемому с помощью процедуры poll, сообщающей о том, есть ли какие-нибудь ожидаемые сообщения. Если есть поступления, то можно вызвать функцию get_message, которая вернет первое поступившее сообщение. В некоторых системах компилятор может вставлять вызовы poll в соответствующие места кода, хотя определить, как часто это следует делать, довольно сложно.

Еще один вариант заключается в схеме, при которой поступающее сообщение самопроизвольно порождает новый поток в адресном пространстве процесса — получателя сообщения. Он называется всплывающим потоком (pop-up thread). Этот поток запускает заранее определенную процедуру, чьим параметром служит указатель на входящее сообщение. После обработки сообщения осуществляются выход из процедуры и автоматическое удаление потока.

Вариант этого замысла предусматривает запуск кода получателя непосредственно в обработчике прерывания, чтобы избежать хлопот с созданием всплывающего потока. Дополнительно ускорить работу этой схемы помогает то, что само сообщение снабжают адресом обработчика, чтобы при поступлении обработчик мог быть вызван всего несколькими командами. Большой выигрыш этой схемы в том, что не требуется никакого копирования. Обработчик забирает сообщение из интерфейсной платы и обрабатывает его на лету. Такая схема называется активными сообщениями (Von Eicken et al., 1992). Поскольку каждое сообщение содержит адрес обработчика, активные сообщения работают только при условии полного доверия между отправителями и получателями.

8.2.4.

<< | >>
Источник: Э. ТАНЕНБАУМ Х. БОС. СОВРЕМЕННЫЕ ОПЕРАЦИОННЫЕ СИСТЕМ Ы 4-е ИЗДАНИЕ. 2015

Еще по теме Сравнение блокирующих и неблокирующих вызовов:

  1. СРАВНЕНИЕ
  2. МЕТОД СРАВНЕНИЯ.
  3. Диахронное и синхронное сравнение
  4. Внутреннее и внешнее сравнение
  5. Нормативное сравнение
  6. 1. Понятие и значение сравнения
  7. ВЫЗОВ ВРАЧА
  8. Функциональное сравнение
  9. СПТ откликается на вызов обстоятельств
  10. Сравнение