非同期ステートマシン:イデオロギーとテクノロジー

エントリー



部下が病気にならず、死なず、常に職場にいて、事前準備なしで注文を実行するのは良いことです。「電話-起きる」。 たとえば、RESTモデルに準拠するWebサービスです(特別なHTTP用語を破棄すると、サービスインターフェイスが実際にデータコンテナーのインターフェイスであるという事実になります)。



実生活では、部下には鼻水と産休があり、ネットワーク接続にはタイムアウトがあり、フライトには天候があり、寒い天候の自動車エンジンにはアイドル時間が必要です。



非同期ステートマシンは、リッチで常に予測可能な内部世界を持つエンティティを管理するための便利なトップレベルの抽象化です。 このようなエンティティは、ハードウェアデバイス、ネットワークプロトコルセッション、または単にコードが制御されていない並列実行プロセスだけです。



以下に説明する非同期ステートマシンのアーキテクチャは、サブシステムの内部状態を考慮して、サブシステムの正面統合中に発生する多くの標準的な問題を解決します。 これらの問題の中で最も目立つのは、信号の本質と状態間の遷移の多様性の欠如(つまり、「ガルバニック絶縁」が不十分だ)です。これにより、マシンはDoS攻撃に対して不安定になります。 サブシステムノードまたはそれによって使用されるリソースの「不十分なアトミックな」置換など、その他のあまり明らかではないものがあります。



解剖学(オブジェクトの分解)



ステートマシンモデルには、次の基本エンティティが含まれます。

  1. 状態は、提供される機能の点で他とは異なる管理対象システムの機能モードです。 したがって、キャッシュとバッファのスナップショット、「フェンスから昼食まで」サイクルのオプション、および管理対象システムの他の事故は、「状態」の概念には含まれません。 州の標準では、いくつかのユニットがあるはずです。 アカウントが2番目の10になった場合-ほとんどの場合、管理対象システムを断片化または階層化する必要があります。
  2. 条件は、システムの「入力」の1つに関する論理値(trueまたはfalse)です。 オートマトンのすべての入力の状​​態の重ね合わせにより、オートマトンのターゲット状態が一意に決まります。 したがって、機械の状態にとって重要な入力信号は、最終的に1つ以上の条件の値を設定することになります。
  3. 反応は、現在の状態とターゲットの違いに対するオートマトンの応答です。 基本的に異なるタイプの反応、つまり状態間の直接遷移、ルート、ストップルート(「ブリック」)の2つ半を数えました。 直接ジャンプは、空の操作(NOP)になることもあります-たとえば、入力の変更が非同期操作の完了の通知によって引き起こされる場合。




繰り返しますが、 状態は一連の条件によって決定され、反応は状態のペアによって決定されます 。 入力の任意のセットは、任意の状態に関連付けることができます。つまり、任意の入力のセットでは、システムの目的の状態を未定義にしないでください。



ルートは「怠lazに」設定されます-直接遷移のない状態の各ペアに対して、中間ターゲット状態が指定されます。 これは、ネットワークパケットのルーティングにおけるゲートウェイに似ていますが、1つの違いがあります。ルートをネストできます。



目的の状態は、各遷移後に再計算されます。 したがって、ルートが最後までカバーされるという保証はありません。 これにより、ステートマシンの「夢想」が減ります。つまり、状況の予期しない変化に対する応答時間が短縮されます。



「停止ルート」は、初期状態と中間状態が一致するルートです。 これは、何も実行すべきではない状態のペア(実際と望ましい)をマークするために使用されますが、目的の状態が変更されるまで待機する必要があります。



Javaで記述する場合は、条件と状態のセットを列挙型として定義し、オートマトンの抽象的な実装では、パラメーター化、「ステータスワード」のEnumSet、およびルートテーブルと遷移テーブルのEnumMapを使用します。 これは教義ではありません。 おそらく、それは何らかの形で異なる可能性があります。 しかし、アルメニアのラジオが言うように、それは残念です。



生理学(スレッド化とシグナル伝達)



メッセージキューを介して非同期状態マシンを構築すると便利です。 Android(android.osパッケージ)には、要件を完全に満たすMessageQueue + Looper + Handlerインフラストラクチャがあります。 「アダルト」Javaでは、単一のスレッドから、またはLinkedBlockingQueueを解析するループだけからThreadPoolExecutorを使用できます。 封鎖リソースの制限がない場合は、茶葉をspareしみ、フロー内の各管理対象システムに割り当てないでください。



クライアントコードへの応答メッセージ(デフォルトでは、これは管理対象システムの状態のIDですが、牛乳、子牛など、いくつかの有用な結果がある可能性があります)は、スレッドセーフなサブスクライバーのリストを介して送信されます (C#では、イベントにサブスクライブするデリゲートは既定で実装されています。)メッセージを送信する方法では、ブールフラグ "I have十分"を返すと便利です。 このようなフラグを使用して行う最も簡単なことは、クライアントが必要な状態を待機できる1回限りの「ラッチ」を実装することです。 ただし、かなり複雑なシナリオを作成できます(「Aが実行した後、Bが実行してからフックを解除した後」)。



入力状態の変化に関するメッセージは、対応するフラグを設定またはリセットすることで簡単に考慮されます。 これらの信号に対して他のアクションは実行されません。 状態間の実際の遷移は、他のメッセージのキューがない場合(または「おそらくない」場合-競合状態を防止せず、身体の動きを減らすだけであるため、ここではスレッドセーフの保証は必要ありません)、つまり「着信メール」が完全に分解された場合に解決されます。



状態遷移コードでいくつかの入力を自分で指定する場合、同期(直接割り当て)と非同期(キューに信号を配置)の両方で行うことができます。 完全な非同期性により、キュー枯渇ハンドラーからループが削除されますが、システム状態が変更された場合(つまり、ルートの停止ループで停止していない場合)に明示的な「ping」(空のメッセージの送信)が必要ですが、ルートは最後まで完了していません。 「目的の状態が実際の状態と等しくないと同時にストップルートに休まなかった」という条件でサイクルを編成することにより、pingを実行せずに実行できます。 どちらの場合も、停止ルートは明示的に区別する必要があります。



教育学(ランレベルとパニック)



一貫して適用されるcontainsAll()に基づいてわかりやすい状態テーブルが作成されます。つまり、入力の「条件ワード」にこの状態に必要なすべての条件が含まれ、すべてのAに同じではない場合、システムは状態A(n)にあると見なされます(m | m <n)。 ほとんどの場合、状態は機会の蓄積(「オフにして使用しない」から「オンにしてすべての準備を整える」まで)と成果(「行く意欲」から「行く」、「到着する」まで)の蓄積に従って厳密にランク付けできます。 この場合、後続の各状態は、必要な条件のセットが厳密に弱くなっているため、前の状態とは異なります。



例外があるかもしれません。 例外的なケースを説明するために、「パニック」フラグが使用されます。これは、適切な状態にあるための必要十分条件です。 この状態への移行により、使用されているすべてのリソースが解放されるか、管理システムが何らかの形で「クリーン」になります。



おそらく、複数のパニックフラグ、複数の例外条件を開始し、「深刻度」の降順でランク付けできますが、実際にはこれはまだ必要ではありません。



経済学(リソース)



あなたが手に入れたリソースは特別な扱いを必要としません。 特に、通常の非最終的な非揮発性プライベートフィールドに保存できます。 このリソースがあるかどうかに関する情報は、現在の状態から完全に導出されます。



特別な処理が必要なリソースは依存関係です。管理対象システムがそれ自体を生成せずに使用するものです。 リソースは外部から提供されます。 ステートマシンへの依存性の注入は、別のパターンで実装されます。これを「機器」と呼びます。



機器の各「スロット」には、永続的な属性が1つあります。このタイプの機器の可用性を示す条件と、推奨値と有効値の2つの値です。 提案されるのは、クライアントによって提案されたものです。 有効なのは実際に使用されるものです。 機器の状態は、キュー枯渇ハンドラーの開始時、および状態間の各遷移後にチェックされます。



機器の状態の確認は次のように実行されます(非ヌル効果が推奨される非ヌルに置き換えられた場合の最も難しいケースを分析します-残りは無関係な「if-then」を削除することで取得されます)。





機器ホルダーオブジェクトのget()メソッドはパケットプライベートであり、有効に戻ります。



オートマトンを異なる方法で同じ問題を解決するコンポジットにグループ化する場合、アクティブなアーティストだけでなく、すべてのアーティストに同じリソースを提供することをお勧めします。 (対照的に、クライアントの注文はアクティブな請負業者にのみ配信されます。)



家族生活の倫理と心理学(統合)



入力信号をその発生源で厳密に分離します。





管理対象システムからのメッセージの中には、他のメッセージを「暗示」または「減価」するものもありますが(たとえば、ロックにキーがないと、取得した速度に関する情報が古くなっています)、管理対象システムからの信号はクライアントの指示をキャンセルせず、クライアントの指示もありません管理対象システムに関する知識を変更しないでください。 これらの条件の論理的混合(加算、乗算)は、実際の状態への「ワード入力」レデューサーで既に発生しています。



衛生(ベストプラクティス)



サブシステムでのすべての操作は、状態間の遷移の順序で実行されることが基本的に重要です。 厳密にすべて。 厳密には、理想的には、オートマトンの特定の実装のクラスでは、遷移テーブルに個別のプライベートメソッド-デリゲート(C#の意味で、Javaでは-CallableまたはRunnableなどの疑似デリゲート)のみが存在するべきではありません。 ある程度まで、これのために、コードの重複を許容することさえできます-その除去のための標準的な方法はそれらに発行されたセマンティクスの冗長性を除去しないので。 (1人のデリゲートが遷移テーブルのいくつかのセルに入力するグループは、セマンティクスの侵食を引き起こさず、通常はコーシャです。骨格実装では、このような機能メソッドがあります。)



それぞれonStateEnter()またはonStateExit()をモデルにした「フック」はありません。 サブシステムがどの「方向」に対応する状態を渡すかを知るか、無視する必要があります。



管理対象システム(ステートマシンのファサードの背後にある「物理」)が愚かなイニシアチブを示していないことを確認してください。つまり、状態自体(エラーの診断を除く)を監視せず、「同時に」または「念のため」何もしません。 これには、遅延初期化と、クライアントの動作を予測および予測する試みが含まれます。 これはすべてエラーの原因です。



レデューサーとルートと遷移のテーブルを初期化した後、明示的に(可能性のあるすべての条件セットと可能性のあるすべての状態のペアの学問的な列挙により)完全性を確認することをお勧めします。



オートマトンを自動的にテストする場合(フーリガン以外の自己繰り返しはごめんなさい)、テスト中に周波数の順序が変わるメッセージを「ロード」します。非常に迅速(1つずつ処理されるよりも著しく高速-キューを満たすこと自体が脅威にならないことを確認します) (反応の頻度に匹敵する頻度-競合状態を確認するため)、反応の頻度より遅い(物理が非同期タスクを開発し、オートマトンが独立した決定を行う際の独立したランレベル「成長」を観察するため) 次のアクション)。



PSパラメーターAの変更のためにクライアントに署名する前に、Aの現在の値を同期的に渡します(「特に正しい-競合状態を防ぐ」)。



おわりに



トリビアをいくつか書きましたが、定期的に読む必要があるコードから判断すると、トリビアはおそらく有用であることが判明しました。



All Articles