Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

The Boost Statechart Library - Rationale

Boost , ,

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

The Boost Statechart Library

Rationale

Introduction
Why yet another state machine framework
State-local storage
Dynamic configurability
Error handling
Asynchronous state machines
User actions: Member functions vs. function objects
Limitations

Introduction

Большинство дизайнерских решений, принятых во время разработки этой библиотеки, являются результатом следующих требований.

Подъем. Statechart должен...

  1. быть полностью безопасным. По возможности, несоответствия типов должны быть отмечены ошибкой во время компиляции.
  2. Не требует использования генератора кода. Многие из существующих FSM-решений вынуждают разработчика проектировать государственную машину либо графически, либо на специализированном языке. Все или часть кода генерируется
  3. позволяет легко трансформировать UML-статус (определяется вhttp://www.omg.org/cgi-bin/doc?formal/03-03-01)в рабочую машину. Напротив, существующая реализация на C++ машины состояний должна быть довольно тривиальной для преобразования в UML. В частности, следует поддерживать следующие функции государственных машин:
    • Иерархические (составные, вложенные) состояния
    • Ортогональные (конкурентные) состояния
    • Вступление, выход и переход
    • Охрана
    • Неглубокая/глубокая история
  4. произвести настраиваемую реакцию, когда исключение C++ распространяется из кода пользователя
  5. поддерживают синхронные и асинхронные машины состояния и оставляют его пользователю, в потоке которого будет работать машина асинхронного состояния. Пользователи также должны иметь возможность использовать библиотеку потоков по своему выбору.
  6. поддержка разработки произвольно больших и сложных государственных машин. Несколько разработчиков смогут работать на одной и той же машине одновременно
  7. позволяет пользователю настроить все управление ресурсами так, чтобы библиотека могла использоваться для приложений с жесткими требованиями реального времени.
  8. как можно больше времени на сборку. В частности, недействительные государственные машины не должны компилироваться.
  9. предлагает разумную производительность для широкого спектра приложений

Why yet another state machine framework?

Прежде чем я начал развивать эту библиотеку, я посмотрел на следующие рамки:

  • Фреймворк, сопровождающий книгу «Практические государственные диаграммы на C/C++» Миро Самека, CMP Books, ISBN: 1-57820-110-1
    http://www.quantum-leaps.com
    Не удовлетворяет по меньшей мере требованиям 1, 3, 4, 6, 8.
  • Фреймворк, сопровождающий «Rhapsody in C++» от ILogix (решение для генератора кода)
    http://www.ilogix.com/sublevel.aspx?id=53
    Это похоже на сравнение яблок с апельсинами. Тем не менее, нет никакой врожденной причины, по которой генератор кода не может производить код, который может быть легко понят и изменен людьми. Не удовлетворяет по крайней мере требованиям 2, 4, 5, 6, 8 (до генерации кода достаточно проверки ошибок).
  • Рамки, сопровождающие статью «Государственный машинный дизайн на C++»
    http://www.ddj.com/184401236?pgno=1
    Не удовлетворяет по меньшей мере требованиям 1, 3, 4, 5 (нет прямой поддержки потоков), 6, 8.

Я верю, что Буст. Statechart удовлетворяет всем требованиям.

State-local storage

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

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

Пункт FAQ «Что так круто в отношении локального хранилища?» далее объясняет это, сравнивая учебник StopWatch с поведенчески эквивалентной версией, которая не использует государственное локальное хранилище.

Dynamic configurability

Two types of state machine frameworks

  • Фреймворк машины состояния поддерживает динамическую настраиваемость, если вся компоновка машины состояния может быть определена во время выполнения («выкладка» относится к состояниям и переходам, действия по-прежнему задаются обычным кодом C++). То есть данные, доступные только во время выполнения, могут быть использованы для создания произвольно больших машин. См. "A Multiple Substring Search Algorithm" by Moishe Halibard and Moshe Rubin in June 2002 issue of CUJ for a good example (unfortunately not available online).
  • С другой стороны, это структуры государственных машин, которые требуют, чтобы макет был указан во время компиляции.

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

Тем не менее, полнофункциональные динамические библиотеки FSM существуют. Итак, вопрос:

Why not use a dynamically configurable FSM library for all state machines?

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

  • Оптимизации и проверки времени компиляции не могут быть сделаны. Например, Boost. Statechart определяетнаиболее распространенный контекстсостояния перехода и назначения во время компиляции. Кроме того, проверка времени компиляции гарантирует, что машина состояния является действительной (например, что нет переходов между ортогональными состояниями).
  • Двойная отправка неизбежно должна осуществляться с помощью какой-то таблицы. Как утверждается вДвойной диспетчерской, это масштабируется плохо.
  • Чтобы обеспечить быстрый поиск стола, состояния и события должны быть представлены целым числом. Чтобы таблица была как можно меньше, нумерация должна быть непрерывной, например, если есть десять состояний, лучше всего использовать идентификаторы 0-9. Для обеспечения непрерывности идентификаторов все состояния лучше всего определять в одном и том же файле заголовка. То же самое касается и событий. Опять же, это не масштабируется.
  • Поскольку события, несущие параметры, не представлены типом, необходимо использовать некоторое общее событие с картой свойств, и безопасность типа обеспечивается во время выполнения, а не во время компиляции.

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

Error handling

Нет ни одного слова об обработке ошибок в спецификациях семантики машины состояния UML. Более того, большинство существующих FSM-решений также, похоже, игнорируют эту проблему.  

Why an FSM library should support error handling

Рассмотрим следующую конфигурацию состояния:

A

Оба состояния определяют действия ввода (x() и y()). Всякий раз, когда состояние A становится активным, вызов x() будет немедленно сопровождаться вызовом y(). y() может зависеть от побочных эффектов x(). Таким образом, выполнение y() не имеет смысла, если x() терпит неудачу. Это не эзотерический угловой случай, но происходит каждый день в государственных машинах все время. Например, x() может приобрести память, содержимое которой позже изменено y(). Существует иная, но с точки зрения обработки ошибок одинаково критическая ситуация в Тюториальной области под Установить государственную информацию из машины, когда Running::~Running() имеет доступ к внешнему состоянию Active. Если бы действие входа Active потерпело неудачу и Running было введено в любом случае, то Running действия выхода ссылались бы на неопределенное поведение. Ситуация обработки ошибок с внешними и внутренними состояниями напоминает ту, которая имеет базовые и производные классы: Если конструктор базового класса терпит неудачу (заявив исключение), строительство прерывается, производный конструктор класса не называется и объект никогда не приходит к жизни.
В большинстве традиционных фреймворков FSM такая ситуация с ошибками относительно проста в решении , если ошибка может быть распространена на клиента государственной машины. В этом случае неудачное действие просто распространяет исключение C++ в рамки. Рамки обычно не захватывают исключение, чтобы клиент государственной машины мог справиться с ним. Обратите внимание, что после этого клиент больше не может использовать объект государственной машины, потому что он находится либо в неизвестном состоянии, либо каркас уже сбросил состояние из-за исключения (например, с защитником сферы охвата). То есть, по своей природе, государственные машины обычно предлагают только основную безопасность исключения.
Тем не менее, обработка ошибок с традиционными фреймворками FSM становится удивительно громоздкой, как только многие действия могут потерпеть неудачу, а государственная машина сама должна изящно справляться с этими ошибками. Обычно, неудачное действие (например, x()) затем публикует соответствующее событие ошибки и устанавливает глобальную переменную ошибок к истине. Каждое последующее действие (например, y()) сначала должно проверить переменную ошибки, прежде чем делать что-либо. После того, как все действия завершены (не делая ничего!), ранее опубликованное событие ошибки должно быть обработано, что приводит к исполнению действия средства правовой защиты. Пожалуйста, обратите внимание, что недостаточно просто очертить событие ошибки, поскольку другие события все еще могут быть отложены. Вместо этого событие ошибки имеет абсолютный приоритет и должно быть рассмотрено немедленно. Есть несколько менее громоздкие подходы к обработке ошибок FSM, но они обычно требуют изменения схемы государственного устройства и, таким образом, затеняют нормальное поведение. Независимо от того, какой подход используется, программисты обычно вынуждены писать много кода, который имеет дело с ошибками, и большая часть этого кода не посвящена обработке ошибок, но распространению ошибок.

Error handling support in Boost.Statechart

Исключения C++ могут распространяться из любого действия, чтобы сигнализировать о провале. В зависимости от того, как настраивается государственная машина, такое исключение немедленно распространяется на клиента государственной машины или вылавливается и превращается в специальное событие, которое отправляется немедленно. Более подробную информацию см. в главе Исполнение в учебнике.

Two stage exit

Действие выхода может быть реализовано путем добавления деструктора в состояние. Из-за природы деструкторов есть два недостатка в этом подходе:

  • Поскольку деструкторы C++ практически никогда не должны бросать, нельзя просто распространять исключение из действия выхода, как это происходит, когда одно из других действий не удается.
  • Когда объектstate_machine<>разрушается, то неизбежно разрушаются и все текущие активные состояния. То есть терминация государственной машины связана с уничтожением государственного машинного объекта.

По моему опыту, ни один из вышеперечисленных пунктов обычно не является проблемой на практике, так как...

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

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

Asynchronous state machines

Requirements

Для асинхронных государственных машин разные приложения имеют довольно разнообразные требования:

  1. В некоторых приложениях каждая государственная машина должна работать в своей собственной нити, другие приложения являются однопоточными и запускают все машины в одной нити.
  2. Для некоторых приложений идеально подходит планировщик FIFO, другим нужны приоритетные или EDF-планировщики.
  3. Для некоторых приложений поддержка::thread-библиотека просто прекрасна, другие могут захотеть использовать другую библиотеку потоков, но другие приложения работают на платформах без ОС, где ISR являются единственным режимом одновременного выполнения.

Out of the box behavior

По умолчанию asynchronous_state_machine<> объекты подтипа обслуживаются объектом fifo_tabler<>. fifo_tabler<> не запирается или не ждет в однонаправленных приложениях и использует примитивы для повышения скорости::thread для выполнения этого в многоадресных программах. Более того, объект fifo_tabler<> может обслуживать произвольное число объектов подтипа asynchronous_state_machine<>. Под капотом fifo_tabler<> является лишь тонкой оберткой вокруг объекта FifoWorker параметр шаблона (который управляет очередью и обеспечивает безопасность потока) и процессор_контейнер<> (который управляет сроком службы государственных машин).

Стандарт UML требует, чтобы событие, не вызвавшее реакцию в государственной машине, было молча отброшено. Поскольку объект fifo_tabler<> сам по себе является государственной машиной, события, предназначенные для того, чтобы больше не существовало объектов asynchronous_state_machine<>, также бесшумно отбрасываются. Это подтверждается тем, что объекты подтипа asynchronous_state_machine<> не могут быть построены или уничтожены непосредственно. Вместо этого, это должно быть сделано через fifo_tabler<>::Создать_процессор<>() и fifo_tabler<>::destroy_processor() (процессор означает, что fifo_tabler<> может разместить только event_processor<>> подтип объектов; asynchronous_state_machine<> является лишь одним из способов реализации такого процессора. Более того, Создать_процессор<>() возвращает только объект процессор_handle. Отныне это должно быть использовано для инициирования, очередей для, прекращения и уничтожения государственной машины через планировщик.

Customization

Если пользователь должен настроить поведение планировщика, она может сделать это, мгновенный fifo_tabler<> с ее собственным классом моделирования концепции FifoWorker. Я рассматривал гораздо более общий дизайн, где блокировка и ожидание реализуются в политике, но я до сих пор не придумал чистый и простой интерфейс для него. Особенно ожидание немного сложно смоделировать, поскольку некоторые платформы имеют переменные состояния, другие имеют события, и все же другие не имеют никакого представления о ожидании чего-либо (они вместо этого петируют, пока не придет новое событие, предположительно через ISR). Учитывая относительно несколько строк кода, необходимых для реализации пользовательского FifoWorker Тип и тот факт, что практически все приложения будут реализовывать как минимум один такой класс, в любом случае это не кажется целесообразным. Приложения, требующие менее или более сложного управления временем процессора событий, могут настроить поведение на более грубом уровне, используя пользовательский тип Scheduler. В настоящее время это относится также и к заявкам, требующим планов выдачи пропусков, не относящихся к ФИФО. Тем не менее, наслаждение. Statechart, вероятно, предоставит Приоритет_планировщик в будущем, чтобы пользовательские планировщики должны быть реализованы только в редких случаях.

User actions: Member functions vs. function objects

Все пользовательские функции (react функции-члены, входные, выходные и переходные действия) должны быть членами класса. Это объясняется следующими причинами:

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

Limitations

Junction points

Точки соединения UML не поддерживаются, потому что произвольно сложные охранные выражения могут быть легко реализованы с помощью custom_reaction>.

Dynamic choice points

В настоящее время нет прямой поддержки этого элемента UML, потому что его поведение часто может быть реализовано с помощью custom_reaction>. В редких случаях это невозможно, а именно когда точка выбора оказывается исходным состоянием. Затем поведение может быть легко реализовано следующим образом:

struct make_choice : sc::event< make_choice > {};
// universal choice point base class template
template< class MostDerived, class Context >
struct choice_point : sc::state< MostDerived, Context >
{
  typedef sc::state< MostDerived, Context > base_type;
  typedef typename base_type::my_context my_context;
  typedef choice_point my_base;
  choice_point( my_context ctx ) : base_type( ctx )
  {
    this->post_event( boost::intrusive_ptr< make_choice >(
      new make_choice() ) );
  }
};
// ...
struct MyChoicePoint;
struct Machine : sc::state_machine< Machine, MyChoicePoint > {};
struct Dest1 : sc::simple_state< Dest1, Machine > {};
struct Dest2 : sc::simple_state< Dest2, Machine > {};
struct Dest3 : sc::simple_state< Dest3, Machine > {};
struct MyChoicePoint : choice_point< MyChoicePoint, Machine >
{
  MyChoicePoint( my_context ctx ) : my_base( ctx ) {}
  sc::result react( const make_choice & )
  {
    if ( /* ... */ )
    {
      return transit< Dest1 >();
    }
    else if ( /* ... */ )
    {
      return transit< Dest2 >();
    }
    else
    {
      return transit< Dest3 >();
    }
  }
};

choice_point<> в настоящее время не является частью Boost. Statechart, главным образом потому, что Я боюсь, что новички могут использовать его в местах, где им будет лучше с custom_reaction<>. Если спрос достаточно высок, я добавлю его в библиотеку.

Deep history of orthogonal regions

Глубокая история государств с ортогональными регионами в настоящее время не поддерживается:

DeepHistoryLimitation1

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

DeepHistoryLimitation2

Конечно, это работает только в том случае, если C, D, E или любое из их прямых или косвенных внутренних состояний не имеют ортогональных областей. Если нет, то этот шаблон должен применяться рекурсивно.

Synchronization (join and fork) bars

JoinAndFork

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

Event dispatch to orthogonal regions

Погром. Алгоритм отправки событий Statechart отличается от того, который указан в оригинале статьи David Harel и в стандарте UML. Оба мандата предусматривают, что каждое мероприятие направляется во все ортогональные области государственной машины. Пример:

EventDispatch

Здесь алгоритм отправки Harel/UML указывает, что машина должна перейти от (B,D) к (C,E) при обработке события EvX. Из-за тонкостей, которые Харель описывает в главе 7 своей статьи , реализация этого алгоритма не только довольно сложна, но и намного медленнее, чем упрощенная версия, используемая Boost. Statechart, который прекращает поиск реакции, как только он нашел подходящий для текущего события. То есть, если бы этот пример был реализован с этой библиотекой, машина не детерминистически переходила бы от (B,D) к (C,D) или (B,E). Эта версия была выбрана потому, что, по моему опыту, в реальных машинах разные ортогональные области часто не уточняют переходы для тех же событий. Для редких случаев, когда они это делают, поведение UML может быть легко эмулировано следующим образом:

SimpleEventDispatch

Transitions across orthogonal regions

TransAcrossOrthRegions

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


Valid HTML 4.01 Transitional

03 Декабрь, 200603 December, 2006[ORIG_END] -->

Copyright © 2003-2006 Andreas Huber Dönni2006 Andreas Huber Dönni[ORIG_END] -->

Distributed under the Boost Software License, Version 1.0. (См. сопроводительный файл LICENSE_1_0.txt или копия на http://www.boost.org/LICENSE_1_0.txt

Статья The Boost Statechart Library - Rationale раздела может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 21:22:54/0.037199020385742/1