2010年11月10日水曜日

非同期呼び出しについてのメモ

非同期呼び出しが絡んだ設計方法についてのメモです。

※以下、実装がマルチスレッドなのかシングルスレッドなのかは無視して考えましょう。

いい感じの本

Enterprise Integration Patterns とかいい感じっす。ECMA Script 系で必要とされるような非同期処理を扱う雰囲気の本ではなくて JMS や MQ 系のメッセージイングサーバを意識した本なのですが、最初の方の章が非同期処理に関する説明がいい感じで載っています。

おいらも読みなおしたいんだけど、日本に置いてきたので今は読めません(´・ω・`)

同期呼び出しのみの場合
スレッド1:処理A -> 処理B -> 処理C のように実行される。

ある処理が非同期処理に依存する場合

すべての処理が非同期だが、すべての処理が依存関係を持つ場合。
スレッド1:処理Aに処理Bをコールバック登録してフォーク 
スレッド2:     処理A実行 -> 処理Bに処理Cをコールバック登録してフォーク
スレッド3:            処理B実行 -> 処理Cにフォーク
スレッド4:                   処理C実行
この手の処理が、かなり面倒。そもそも概念的に非同期処理でないものを実装上強引に非同期処理にしているだけであり、同時並行処理のメリットが全く無い(シングルスレッド環境などの制約条件下でしかたなく対応するケースがほとんどだと思われ)。しかし、依存する処理をコールバックに変換するだけなので、同期呼び出しの場合からの機械的な変換は可能。この手の設計になる場合、同期処理で擬似コードを書いてから、その後機械的に非同期処理に変換したほうがいいかも。

非同期呼び出しにより処理がフォークする場合

各処理に依存関係がない場合は以下のように4つのスレッドが別々に動作する。
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A
スレッド3:                               -> 処理B
スレッド4:                                               → 処理C
スレッド1の動作は一瞬にして行われるので、処理A,B,Cはほぼ同時に開始する。このような非同期処理は、人間から見てもそこそこ直感的。多くの場合シーケンシャルに動作させた場合よりも高い性能が得られる。

非同期呼び出し処理がジョインする場合

処理A,B,Cのすべてが完了したあとに次の処理が行われる場合の例
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A -> 合流条件確認 -> (失敗)
スレッド3:                               -> 処理B                         -> 合流条件確認 -> (成功) -> 合流後の処理
スレッド4:                                                -> 処理C -> 合流条件確認 -> (失敗)
非同期処理のパフォーマンスを享受しつつ、処理の同期もおこなってしまうというおいしいところ取りですね。ジョインする条件の確認や、その条件を構成する情報の登録などをする必要があるのでコーディングレベルでは少しめんどくさいのですが、事前に設計をきちんとやっておけば問題ないでしょう。

設計方針考察

まずは、fork と join の発生する処理は避けて実装する。非同期処理を利用するとしても陸上のリレーのようにコールバックを用いて処理を継続させる方法であれば、join 条件のチェックが必要無いので設計がシンプルになる。この方法で、一通り実装して、実装した機能が仕様を満たしていることを確認する。

以下書きかけ。
  1. 設計対象の1つの処理を決定する。
  2. 処理内にて順次処理とフォークする処理とジョイン後の処理を、最外殻のレベルで分類し、各々の処理の塊をグループとする。
  3. 上記グループのうち、最外殻のジョイン処理とそれにジョインされる処理を1つのグループとして扱う。
  4. 複数のグループが残った場合、上記の各グループをメソッド化し、それそれを順次実行する。(各々が依存関係を持たないため並行動作が可能)
  5. 単一の順次処理の場合はそのまま記述すればよい。非同期処理は必要ない。
  6. 単一のジョインされるグループが残った場合、ジョイン実行条件のための情報(ジョイン実行条件判断クロージャから見えるスコープのフラグなど)を用意し、ジョイン実行条件を判断するクロージャを用意し、ジョイン実行条件のための情報の変更を通知するためのクロージャを用意し、ジョイン後の処理のクロージャを用意し、残りのグループを各メソッドとして作成して引数にジョイン実行条件判断用のクロージャと情報通知用クロージャとジョイン後の処理のクロージャを渡すようにする。
以降、上記の1からをネストして行う。
この説明じゃおいら自身以外にはわからんよねw まぁ、個人的なメモなので、、、。

まとめ

同一スレッドでの順次処理、フォーク、ジョインの3種類を適切に考えて設計すればよいということですね。

このような概念を用いた設計はプログラミング言語で記述することは直感的ではないと思うので(少なくともおいらには)、コーディングに入る前にアクティビティ図のような形で設計してみたほうがよいと思います。

0 件のコメント:

コメントを投稿