Вот более общий ответ, который заменяет мой предыдущий ответ, основанный на стеках вызовов. Поскольку предыдущий ответ был принят, я не буду заменять текст.
Пролог
В некоторых архитектурах нет вещей, которые они называют «функциями», которые «вызываются», но у них есть нечто аналогичное (обмен сообщениями может называть их «методами» или «обработчиками сообщений»; архитектуры, основанные на событиях, имеют «обработчики событий» или просто «обработчики»). ). Я буду использовать термины «блок кода» и «вызов» для общего случая, хотя (строго говоря) «блок кода» может включать в себя вещи, которые не являются полностью функциональными. Вы можете заменить «invocation» или «invoke» соответствующей изменяемой формой «call», как я мог бы в некоторых местах. Характеристики архитектуры, описывающие вызов, иногда называют «стилями», например, «стилем передачи продолжения» (CPS), хотя ранее это не было официальным термином. Чтобы вещи не были слишком абстрактными, мы рассмотрим стек вызовов, передачу продолжения, обмен сообщениями (а-ля ООП) и стили вызова обработки событий. Я должен указать модели, которые я использую для этих стилей, но я опускаю их из соображений экономии места.
Особенности вызова
или, C для продолжения, координации и контекста, для меня этого достаточно
Hohpe идентифицирует три красиво аллитерационные функции вызова стиля стека вызовов: продолжение, координация, Контекст (все с заглавной буквы, чтобы отличить их от других употреблений слов).
- Продолжение решает, где будет продолжено выполнение после завершения блока кода. Функция «Продолжение» связана с «первоклассными продолжениями» (часто называемыми просто «продолжениями ", в том числе мной), поскольку продолжения делают функцию продолжения видимой и управляемой на программном уровне.
- Координация означает, что код не выполняется, пока не будут готовы необходимые данные. В пределах одного стека вызовов вы получаете координацию бесплатно, потому что счетчик программы не вернется к функции, пока вызываемая функция не завершится. Координация становится проблемой (например) при параллельном программировании и программировании, управляемом событиями: первое связано с тем, что производитель данных может отставать от потребителя данных, а второе - потому, что, когда обработчик запускает событие, обработчик немедленно продолжает работу, не дожидаясь ответа.
- Контекст относится к среде, которая используется для разрешения имен в блоке кода. Он включает выделение и инициализацию локальных переменных, параметров и возвращаемых значений. Передача параметров также регулируется соглашением о вызовах (соблюдение аллитерации); в общем случае вы можете разделить Context на функцию, которая охватывает локальных жителей, одну, которая охватывает параметры, а другая - для возвращаемых значений. Для CPS возвращаемые значения покрываются передачей параметров.
Эти три функции не обязательно независимы; стиль вызова определяет их взаимоотношения. Например, координация привязана к продолжению в стиле стека вызовов. Продолжение и Контекст связаны в целом, поскольку возвращаемые значения участвуют в продолжении.
Список Хопе не обязательно исчерпывающий, но его будет достаточно, чтобы отличить хвостовые вызовы от gotos. Предупреждение: я могу пойти по сторонам, например, исследовать пространство вызова на основе особенностей Hohpe, но я постараюсь сдержаться.
Задачи функции вызова
Каждая функция вызова включает задачи, которые должны быть выполнены при вызове блока кода. Для продолжения вызываемые блоки кода естественным образом связаны цепочкой вызывающего кода. Когда вызывается блок кода, текущая цепочка вызовов (или «цепочка вызовов») расширяется путем размещения ссылки («ссылка на вызов») на вызывающий код в конце цепочки (этот процесс более конкретно описан ниже) . Принимая во внимание, что вызов также включает привязку имен к кодовым блокам и параметрам, мы видим, что даже языки, не связанные с зависимостью и дисциплиной, могут иметь такое же удовольствие.
Хвостовые звонки
или Ответ
или остальное в принципе не нужно
Хвостовой вызов - это оптимизация продолжения, и это вопрос распознавания того, когда можно пропустить основную задачу продолжения (запись ссылки на вызов). Остальные функциональные задачи стоят сами по себе. «Перейти» представляет собой оптимизацию задач для продолжения и контекста. В значительной степени поэтому хвостовой вызов - это не просто «goto». Дальнейшее поясняет, как выглядят хвостовые вызовы в различных стилях вызова.
Хвостовые вызовы в определенных стилях вызова
Различные стили упорядочивают цепочки вызовов в разных структурах, которые я назову «клубок», за неимением лучшего слова. Разве не приятно, что мы отказались от спагетти-кода?
- Со стеком вызовов в клубке есть только одна цепочка вызовов; удлинение цепочки означает нажатие счетчика программ. Хвостовой вызов означает, что счетчик программы не выдвигается.
- В CPS клубок состоит из существующих продолжений, которые образуют обратный arborescence (направленное дерево, каждое ребро которого указывает на центральный узел), где каждый путь возвращается к центру представляет собой цепочку вызовов (примечание: если точка входа в программу передается "нулевым" продолжением, клубок может быть целым лесом обратных ветвей). По умолчанию используется одна конкретная цепочка, в которую во время вызова добавляется ссылка на вызов. Хвостовые вызовы не добавляют ссылку на вызов в цепочку вызовов по умолчанию. Обратите внимание, что «цепочка вызовов» здесь в основном синонимична «продолжению» в смысле «продолжение первого класса».
- При передаче сообщений цепочка вызовов - это цепочка заблокированных методов, каждый из которых ожидает ответа от метода перед ним в цепочке. Метод, вызывающий другого, называется «клиентом»; вызываемый метод - это «поставщик» (я намеренно не использую «сервис», хотя «поставщик» не намного лучше). Связка сообщений - это набор несвязанных цепочек вызовов. Эта структура путаницы больше похожа на наличие нескольких стеков потоков или процессов. Когда метод просто повторяет ответ другого метода как свой собственный, метод может заставить своего клиента ждать своего поставщика, а не самого себя. Обратите внимание, что это дает немного более общую оптимизацию, которая включает оптимизацию координации, а также продолжения. Если последняя часть метода не зависит от ответа (и ответ не зависит от данных, обработанных в последней части), метод может продолжаться после того, как он будет передан поставщику в зависимости от ожидания клиента. Это аналогично запуску нового потока, где последняя часть метода становится основной функцией потока, за которой следует хвостовой вызов в стиле стека вызовов.
А как насчет стиля обработки событий?
При обработке событий у вызовов нет ответов, а обработчики не ждут, поэтому «цепочки вызовов» (используемые выше) бесполезны. Вместо путаницы у вас есть приоритетные очереди событий, которые принадлежат каналам, и подписки, которые представляют собой списки пар слушатель-обработчик. В некоторых архитектурах, управляемых событиями, каналы являются свойствами слушателей; каждому слушателю принадлежит ровно один канал, поэтому каналы становятся синонимами слушателей. Вызов означает запуск события на канале, который вызывает все подписанные обработчики-слушатели; параметры передаются как свойства события. Код, который будет зависеть от ответа в другом стиле, становится отдельным обработчиком при обработке событий со связанным событием. Хвостовой вызов - это обработчик, который запускает событие на другом канале и больше ничего не делает после этого. Оптимизация хвостового вызова будет включать в себя повторную подписку слушателей для события со второго канала на первый или, возможно, срабатывание обработчика, который инициировал событие на первом канале, а не на втором канале (оптимизация, сделанная программистом, а не компилятором /устный переводчик). Вот как выглядит бывшая оптимизация, начиная с неоптимизированной версии.
- Слушатель Алиса подписывается на событие «инаугурация» на BBC News, используя обработчик «party»
- Алиса запускает мероприятие «Выборы» на канале BBC News
- Боб ожидает "выборы" на BBC News, поэтому вызывается обработчик "openPolls" Боба.
- Боб подписывается на событие «инаугурация» на канале CNN.
- Боб запускает мероприятие «голосование» на канале CNN
- Другие события запускаются и обрабатываются. В конце концов, один из них (например, «победа») запускает событие «инаугурация» на CNN.
- Запрещенный куратор Боба объявил "инаугурацию" на BBC News
- Вызывается обработчик инаугурации Алисы.
И оптимизированная версия:
- Слушательница Алиса подписывается на событие «инаугурация» на BBC News.
- Алиса запускает мероприятие «Выборы» на канале BBC News
- Боб ожидает "выборы" на BBC News, поэтому вызывается обработчик "openPolls" Боба.
- Боб подписывает всех, кто слушает «инаугурацию» на BBC News, на мероприятие инаугурации на CNN *.
- Боб запускает мероприятие «голосование» на канале CNN
- Другие события запускаются и обрабатываются. В конце концов, один из них запускает мероприятие «инаугурация» на CNN.
- Обработчик инаугурации Алисы вызывается для инаугурации на CNN.
Обратите внимание, что хвостовые вызовы сложнее (несостоятельны?) При обработке событий, потому что они должны учитывать подписки. Если бы Алиса позже отказалась от подписки на «инаугурацию» на BBC News, подписку на инаугурацию на CNN также пришлось бы отменить. Кроме того, система должна гарантировать, что она не вызывает ненадлежащим образом обработчик несколько раз для слушателя. Что, если в приведенном выше оптимизированном примере есть другой обработчик «инаугурации» на CNN, который запускает «инаугурацию» на BBC News? Событие "вечеринки" Алисы будет запущено дважды, что может вызвать у нее проблемы на работе. Одно из решений состоит в том, чтобы * Боб отменил подписку всех слушателей на «инаугурацию» на BBC News на шаге 4, но затем вы вводите другую ошибку, при которой Алиса будет пропускать инаугурации, которые не поступают через CNN. Может быть, она хочет отпраздновать инаугурации в США и Великобритании. Эти проблемы возникают из-за того, что я не делаю различий в модели, возможно, на основе типов подписок. Например, возможно, существует особый вид одноразовой подписки (например, Обработчики сигналов System-V) или некоторые обработчики сами отписываются, и оптимизация хвостового вызова применяется только в этих случаях.
Что дальше?
Вы можете перейти к более полному определению задач функции вызова. Отсюда вы можете выяснить, какие оптимизации возможны и когда их можно использовать. Возможно, удастся идентифицировать другие функции вызова. Вы также можете придумать больше примеров стилей вызова. Вы также можете изучить зависимости между функциями вызова. Например, синхронный и асинхронный вызовы включают явное связывание или разъединение продолжения и координации. Это никогда не кончится.
Получите все это? Я все еще пытаюсь переварить это сам.
Использованная литература:
- Хохпе, Грегор; "Event-Driven Architecture"
- Сугальский, Дэн; "CPS и хвостовые вызовы - два отличных вкуса, которые прекрасно сочетаются друг с другом "
person
outis
schedule
30.09.2009