Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Прием буферизованного сообщения



Одним из самых ранних дизайнерских решений в Erlang было использование формы буферизации выборочного приема. Синтаксис для приема сообщений в современном Erlang следует базовому шаблону:

receivePattern1 -> Actions1; Pattern2 -> Actions2;...end

Это значит ждать сообщения. Если сообщение соответствует Pattern1, тогда оцените код Actions1. Если сообщение соответствует Pattern2, оцените код Actions2 и т. д. Но что произойдет, если какое-либо другое сообщение, которое не соответствует ни Pattern1, ни Pattern2, поступит в процессы? Ответ - проигнорируйте сообщение и поставьте его в очередь на потом. Сообщение помещается в «очередь сохранения» и обрабатывается позже, а оператор получения продолжает ожидать сообщений, в которых он заинтересован.

Мотивация для автоматической буферизации неупорядоченных сообщений возникла из двух наблюдений. Во-первых, мы заметили, что на языке спецификации и описания CCITT (SDL широко используется в телекоммуникационной отрасли для определения протоколов связи), одна из наиболее часто используемых идиом спецификаций заключалась в организации очереди и последующем воспроизведении сообщений, вышедших из строя. Во-вторых, мы заметили, что обработка сообщений с нарушением порядка в автомате конечных состояний привела к взрыву в пространстве состояний машины конечных состояний. Это происходит чаще, чем вы думаете, в частности, при обработке удаленных вызовов процедур. Большинство телекоммуникационных программ, которые мы пишем, имеют дело с протоколами, ориентированными на сообщения. При реализации протоколов мы должны обрабатывать сообщения, содержащиеся в самом протоколе, вместе с большим количеством сообщений, которые не являются частью протокола, но поступают от удаленных вызовов процедур, выполняемых внутри системы. Наша стратегия организации очередей позволяет нам выполнять внутренний удаленный вызов процедур внутри системы и блокировать до тех пор, пока этот вызов не вернется. Любые сообщения, поступающие во время удаленного вызова процедуры, просто ставятся в очередь и обслуживаются после завершения удаленного вызова процедуры. Альтернативой может быть предоставление возможности обработки протокольных сообщений в середине удаленного вызова процедуры, что значительно увеличивает сложность кода.

Это было большим улучшением по сравнению с PLEX, где каждое сообщение должно обрабатываться, когда оно приходит. Если сообщение приходит «слишком рано», программа должна сохранить его на потом. Позже, когда он ожидает сообщение, он должен проверить, уже пришло ли сообщение.

Этот механизм также чрезвычайно полезен для программирования наборов параллельных процессов, когда вы не знаете, в каком порядке поступит сообщение между процессами. Предположим, вы хотите отправить три сообщения M1, M2 и M3 трем различным процессам и получить ответы R1, R2 и R3 от этих трех процессов. Проблема в том, что ответные сообщения могут поступать в любом порядке. Используя наше утверждение получения, мы могли бы написать:

A! M1, B! M2, C! M3, receive A? R1 ->     receive       B? R2 ->           receive             C? R3 ->                ... now R1 R2 and R3                   have been received...

Здесь A! M означает отправить сообщение M процессу A и А? Х означает получить сообщение Х от процесса А.

Теперь не имеет значения, в каком порядке принимаются сообщения. Код написан так, как будто A отвечает первым, но если сообщение от B отвечает первым, сообщение будет поставлено в очередь, и система будет ожидать сообщения от A. Без использования буфера было бы шесть различных порядков сообщения, которые надо было бы учесть.

Обработка ошибок

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

Это означает, что модель для обработки ошибок основана на идее двух компьютеров, которые наблюдают друг за другом. Обнаружение и устранение ошибок выполняется на удаленном компьютере, а не на локальном компьютере. Это связано с тем, что в наихудшем из возможных случаев произошел сбой компьютера, на котором произошла ошибка, и, следовательно, дальнейшие вычисления на аварийном компьютере выполнить невозможно.

При разработке Erlang мы хотели абстрагировать все аппаратное обеспечение как реактивные объекты. Объекты должны иметь «семантику процесса», другими словами, что касается программного обеспечения, единственным способом взаимодействия с оборудованием является передача сообщений. Когда вы отправляете сообщение процессу, не должно быть способа узнать, действительно ли этот процесс был каким-то аппаратным устройством или просто другим программным процессом. Причина этого состояла в том, что для упрощения нашей модели программирования мы хотели моделировать все как процессы и хотели единообразно взаимодействовать со всеми процессами. С этой точки зрения мы хотели, чтобы программные ошибки обрабатывались точно так же манера как аппаратные ошибки. Так, например, если процесс умер из-за деления на ноль, он будет распространять сигнал {'EXIT', Pid, divByZero} всем процессам в своем наборе ссылок. Если он умер из-за аппаратной ошибки, он мог бы передать сигнал {'EXIT', Pid, ​ ​ machineFailure} своим соседям. С точки зрения программиста, не будет никакой разницы в том, как обрабатываются эти сигналы.

Ссылки

Ссылки в Erlang предназначены для контроля путей распространения ошибок между процессами. Процесс Erlang прекратится, если он выполнит недопустимый код, например, если процесс попытается разделить на ноль. Основная модель обработки ошибок заключается в предположении, что какой-то другой процесс в системе будет наблюдать за его завершением и предпринимать соответствующие корректирующие действия. Но какой процесс в системе должен это сделать? Если в системе несколько тысяч процессов, то как мы узнаем, какой процесс сообщит об ошибке? Ответ - связанный процесс. Если какой-то процесс A связывается с процессом B с помощью ссылки link(B), то оно становится связанным с A. Если A умирает, то B получает информацию. Если B умирает, то A информируется.

Используя ссылки, мы можем создавать наборы процессов, которые связаны между собой. Если это нормальные процессы (Процессы либо нормальные, либо системные процессы. Системный процесс может перехватывать и обрабатывать ненормальные сигналы выхода. Обычные процессы просто умирают), процессы немедленно умирают, если они связаны с процессом, который умирает с ошибкой. Идея здесь состоит в том, чтобы создать наборы процессов, так что если какой-либо процесс в наборе умрет, то все они умрут. Этот механизм обеспечивает инвариант, что либо все процессы в наборе являются живыми, либо ни один из них не является. Это очень полезно для программирования стратегий восстановления после ошибок в сложных ситуациях. Насколько я знаю, ни один другой язык программирования не имеет ничего подобного.

Идея связей и механизма, с помощью которого все процессы в наборе умирают, принадлежала Майку Уильямсу. Идея Майка была вдохновлена дизайном механизма разблокировки, используемого в старых аналоговых телефонах и станциях. В аналоговых телефонах и на ранних электромеханических станциях к телефонам были подключены три провода, называемые А, В и С. Провод С возвращался коммутатору и через все электромеханические реле, участвующие в установлении вызова. Если что-то пошло не так или один из партнеров прекратил вызов, провод C был заземлен. Заземление провода C вызвало эффект обмена, который высвободил бы все ресурсы, подключенные к линии C.

Буферы

Буферы и оператор receive соответствуют друг другу довольно неочевидным образом. Сообщения, отправленные между процессами Erlang, всегда доставляются как можно скорее. Каждый процесс имеет «почтовый ящик», где хранятся все входящие сообщения. Когда приходит сообщение, оно помещается в почтовый ящик, и процесс назначается для выполнения. Когда процесс запланирован в следующий раз, он пытается сопоставить шаблон с сообщением. Если совпадение выполнено успешно, происходит прием сообщения, и сообщение удаляется из почтового ящика, а данные из сообщения попадают в программу. В противном случае сообщение помещается в очередь «сохранения». Когда какое-либо сообщение совпадает, вся очередь сохранения сливается обратно в почтовый ящик. Вся буферизация, которая имеет место, затем выполняется неявно в почтовом ящике или в очереди сохранения. Примерно в 1988 году этот механизм стал предметом интенсивных дебатов. Я помню что-то вроде четырехдневного совещания, посвященного единственной теме того, как на самом деле должна работать межпроцессное взаимодействие.

Результатом этой встречи было то, что мы все решили, что процессы должны взаимодействовать через каналы, и что каналы должны быть первоклассными объектами. Они должны иметь бесконечную (теоретически) буферную емкость, они могут иметь имя и должна быть возможность подключать и отключать их от процессов. Должна быть возможность связывания, разделения и соединения, и я даже создал алгебру каналов. Затем я попытался реализовать её, но это оказалось очень трудным. В частности, операции соединения каналов и объединения (например, если канал X имеет конечные точки A и B, а кнал Y имеет конечные точки C и D, то объединение X и Y было выполнено с использованием муфельных каналов (B, C)) операции были ужасно сложными для программирования. В реализации требовалось два типа сообщений в каналах: обычные сообщения и небольшие трассирующие сообщения, которые нужно было отправлять вверх и вниз по каналам, чтобы убедиться, что они пусты. Иногда каналы приходилось блокировать на короткие промежутки времени, и то, что происходило бы, если процессы на конце каналов выходили бы из строя, было чрезвычайно трудно отследить. Все проблемы проистекают из того факта, что каналы вводят зависимости между конечными точками, так что процессы на обоих концах больше не являются независимыми, что усложняет жизнь.
После двух недель программирования я заявил, что теперь работает конвейерный механизм. На следующий день я выбросил все это - сложность реализации убедила меня в том, что механизм был неправильным. Затем я реализовал механизм двухточечной связи с почтовыми ящиками. Это заняло пару часов. Кажется, это довольно распространенный паттерн: сначала я провожу неделю, реализуя что-то, затем, когда все готово, я выбрасываю все и переопределяю что-то немного другое за очень короткое время.
В этот момент каналы были отклонены, а почтовые ящики приняты.


Поделиться:



Последнее изменение этой страницы: 2019-10-24; Просмотров: 175; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.014 с.)
Главная | Случайная страница | Обратная связь