Pattern Oriented Software Architectureについて.

📚ソフトウェアアーキテクチャ - ソフトウェア開発のためのパターン体系(POSA-1)

混沌から構造へ

複雑になりがちなソフトウェアの構造を整理して「分割して統治」するためのパターン.

Layers

アプリケーションを複数の”層”に分け, それらを独立したモジュールとして開発・保守する. 各層はインタフェースを定義しモジュール化されたソフトウェアであり, テクノロジーの進歩や要求の変化に合わせて各層を個別に置換できる.

分割が必要な大規模なシステムが前提. 小規模システムではいらない.

  • 特徴

    • 隣接しないレイヤへのアクセスを禁止
    • 片方向のアクセスを許可.
  • メリット

    1. 変更が局所化されるため保守性が向上する,
    2. 抽象度の低い下位レイヤは, さまざまなシステムで再利用できる可能性がある
    3. レイヤ間のやり取りが標準化されている場合, レイヤを交換することが容易になる

Pipes and Filters

lackboard

分散システム

分散システムを開発する上で有効なパターン.

Broker

対話型システム

人とコンピュータの対話 (クライアントとシステムの対話) を提供するシステムで有効なパターン

Model-View-Controller

Presentation-Abstraction-Control

適合型システム

環境の変化や機能要求の変化による中核機能への影響を抑えるために有効なパターン.

Microkernel

Reflection

References

📚Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects(POSA-2)

オブジェクト指向設計と並列プログラミングをAndoirdで学ぶ!Pattern-Oriented Software Architecturesを受けた | Futurismo

Doug 先生, 渾身の著作! なぞの PDF 群.

とても参考になるリンク. POSA2.

Service Access and Configuration Patterns

Wrapper Facade

Wrapper Facade Pattern について調べてみたまとめ | Futurismo

ソケットなどの OS に依存するような native methods に対してラッパーライブラリを作成すること.

encapsulate low-level functions and data
structures with object-oriented (OO) class interfaces.

以下のメリットがある.

  • Non-OOP 言語が OOP で利用できたり (Type-safe I/F)
  • OS 依存がなくなったり, (Portable)
  • コンパイル時にエラーをチェックする機能を作り込んだりして,API error-prone (誤りがちな) を防ぐ.

Facade や Bridge と異なり, 薄く, Light weight な実装でオーバヘッドがないことが特徴.

coursera の posa 講義で出てきたパターン. Doug 氏 の論文.

言語レベルでサポートされていることが多い? Ruby の Socket Library がよい例. bind や listen を TCPServer メソッドで隠している.

server = TCPServer.new (4481)
 
# =>
server = Socket.new (:INET, :STREAM)
addr = Socket.pack_sockaddr_in (4481, "0.0.0.0")
server.bind (addr)
server.listen (5)

Component Configurator

Interceptor

Extension Interface

Event Handling Patterns

Reactor (Evented)

Reactor Pattern について調べてみたまとめ | Futurismo

イベント駆動のためのパターン. 同期的なイベント処理.

The reactor design pattern is an event handling pattern
for handling service requests delivered concurrently
to a service handler by one or more inputs.

Networking で利用される Reactor は, select を使った狭義の Reactor Pattern.

  • 特徴

    イベント (入力データ) を,処理や状態を表すハンドラ (メソッド or サブクラス) にディスパッチ (結びつける) する.

    ハンドラごとに責務を分割できるので OOP 向き.

    EventMachine は Reactor パターンの高性能な実装さ.

    Android は状態ごとにハンドラが呼び出される.

    • onCreate ()
    • onResume ()
    • onDestory ()

    Spring Framework では, GET や POST の HTTP request は, それぞれ対応するメソッドにコールバックされる.

  • Structure

    • Resources: 入力データ
    • Synchronous Event Demultiplexer:入力データを拾うためのイベントループ.
      • シングルスレッドで loop していることが特徴 (Synchronous).
      • Dispatcher: ハンドラを管理する. Demultiplexer から通知を受けたら,
        • Resource を Handler に Dispatch する (select など)
      • Handler: Resource に関連付けられた処理.

Proactor

非同期なイベント処理.

POSIX の aio を利用して実現する.

非同期に I/O 操作を実施して, 終了時にはコールバックで通知を受け取る. Non-Blocking.

  • Reactor と Proactor の違い

    ここが もとネタ.

    日本語の解説: C++ vs. Java vs. C#のネットワークパフォーマンス比較 - 井上

    Reactor パターン

    select (2) or poll (2) でソケットの監視をして, non-blocking なrecv (2), send (2) で送受信します.

    ソケットが読み書きできるようになるまでカーネルで待機して, 読み書きできるようになるとユーザアプリに制御が来ます. ユーザアプリはrecv で読み込んだデータをバッファにコピー (読み込み時) するか, バッファにデータを書き込んで send を呼びます. 実際のネットワーク上へのデータ送受信は, カーネルの仕事です.


    Proactor パターン

    非同期 IO の API (aio_の prefix の付いた API) で送受信します. ネットワークの送受信が完了した時点で, カーネルからユーザアプリに制御が来ます. 読み込みの場合, バッファに読み込みデータが既に入っているので, ユーザアプリはそれを使えます.


    http://subtech.g.hatena.ne.jp/mala/20090920/1253447692

    ファイルディスクリプタにデータを書き込む場合だと

    • Reactor パターンでは「 writable になるのを待つ, 書き込めるだけ書き込む」というのを全ての書き込みが終わるまで繰り返します.
    • Proactor パターンでは, カーネルに転送を任せて, 転送が完了した時点でイベントが発生します.

    極端な話, 1byte ずつしかデータを送れないデバイスがあったとして

    • Reactor パターンで 1MB 送ろうとした場合, 100 万回 (100 万 * n) のコンテキストスイッチが発生する.
    • Proactor パターンの場合は, カーネルに 1MB のデータを送っておいて, と依頼して終わったらユーザーモードに処理が戻る. コンテキストスイッチは最小限で済みます.

    どちらが優れているか?

    • ユーザーモードとカーネルモードのコンテキストスイッチのコスト.
    • 加えて: イベント駆動モデルで書いている場合は, 関数呼び出しのコスト.
    • 加えて: スレッドモデルで書いている場合は, スレッド or 軽量スレッドのコンテキストスイッチのコスト.

    が, 発生することになります. このコストが無視できないほど大きい場合, Proactor パターンの優位性が大きくなる, ということになるでしょう.

    The main difference between reactor and proactor is the way they do the actual write/read.

    • the reactor is only being signalled when the socket is ready to write/read and then does.the reading/writing itself in a synchronous manor
    • the proactor will use an asynchronous I/O call to the operating system and only supply the buffers to read from/write to.read from/write to.

Asynchronous Completion Token

Acceptor-Connector

通信の初期化処理と実際の処理を分離することで, 構造を整理するパターン.

Reactor Pattern において, Handler と Reactor の仲介を行う. Handler に Dispatch する前に, 一連の手続きが必要な場合には,

  • Acceptor が Handler に対して手続きを実施してから (Accept)
  • Reactor から Handler への Dispatch をさせる (Connect)

Acceptor がサーバ側の仲介者, Connector がクライアント側の仲介者.

クラス図的には,EventHandler のサブクラスに Handler や Acceptor, Connector がいる.

EventHandler

  • CifsAcceptor
    • negotiate
    • session
  • CifsHandler
    • read
    • write
  • CifsConnector
    • negotiate
    • session

Synchronization Patterns

Scoped Locking

クラスのコンストラクタて lock して,デコンストラクタで Unlock する.

Strategized Locking

Thread-Safe Interface

Double-checked locking

ロックの取得におけるオーバヘッドを削減するための技法. まずをスレッドセーフでない方法で「ロックヒント」を調べて, それが成功したら実際のロックを試みる.

Concurrency Patterns

Active Object (Actor)

メソッドの呼び出しとメソッドの実際の実行を分離することで並行性を導入する. 各オブジェクトは利用者からの要求を管理するためのメッセージキューとスケジューラを持つ.

外部の Client スレッドから非同期にメッセージを受け取っても, 自分固有の Active Object スレッドで, 自分の都合のいいタイミングで処理を実行させたい場合に利用する.

起動 (invocation) と実行 (execution) の分離.

Active Object defines units of concurrency as service requests on components & runs service requests on a component in a different thread from the requesting client thread.

  • 他のパターンとの関係

    Acceptor-Connector / Reactor

    Acceptor-Connector Pattern における Handler 部分をスレッド化すればよい. (i.e. Thread per connection) Reactor の Minor Change で実現できる.


    Half-Sync/Half-Async

    Active Object はスレッド/ スレッドを分離する. Half-Sync/Half-Async は, 非同期プロセスと同期プロセスを分離する.

Monitor Object

排他的に実行しなければならないメソッド群を持つオブジェクトをスレッドセーフに利用できるようにするための機構. Java はこれを言語レベルでサポートしている.

Half-Sync/Half-Async Pattern と組み合わせて利用することで, busy の制御や暇な時の過度な負荷を避ける.

Half-Sync/Half-Async

非同期サービスと同期サービスを分離する. 非同期レイヤと同期レイヤはキューでつながっている.

非同期処理と同期処理を共存させて, 構造をシンプルにできる.

The Half-Sync/Half-Async pattern decouples async & sync service processing in concurrent systems, to simplify programming without unduly reducing performance.
  • Example of Networking Programming

    このパターンは OS や GUI Framework で広く利用されている.

    OS 上で動くネットワーククライアントは,ソケットに read/write する. このとき, ソケットがキューになって, Kernel が物理 NIC とやりとりをする.

    • User-Level Processes
      • Sync User Process 1
      • Sync User Process 2
      • Sync User Process 3
    • Socket Layer
      • Socket Queues
    • BSD UNIX Kernel
      • Async Protocol Processing
      • NIC

Leader/Followers

Preforking or Thread Pool 参照.

事前に スレッドを作っておくので, オーバヘッドが小さい.

Thread-specific storage

静的変数・グローバル変数のように扱えるがスレッドごとに異なる内容を格納できるメモリ領域を提供する.

POSA-3

POSA-4

POSA-5

Network Architecture Patterns

pWorking with TCP Sockets から.

Serial

  • advantage
    • Structure is simple.
    • Resource usage is simple.
  • disadvantage
    • No concurrency.

Process per connection

connection ごとに 子プロセスを起動する. connection を accept したあとにプロセスを生成する.

  • advantage
    • simple
    • less overhead
  • disadvantage
    • Unix system 限定
    • プロセス数には限界がある

Thread per connection

connection ごとに スレッドを起動する. connection を accept したあとにスレッドを生成する.

  • advantage
    • process per connection よりも 低 resource, overhead
  • disadvantage
    • スレッド数には限界がある

Preforking

connection ごとに 子プロセスを起動する. あらかじめ process を必要数起動しておき, accept したらそのプロセスに処理を実施させる.

  • advantage
    • 事前にプロセスを起動しておくので, accept 時の overhead がない.
    • メモリの排他を意識する必要がない.
  • disadvantage
    • スレッドに比べてより多くのメモリを利用する.

Thread Pool

connection ごとに スレッドを起動する. あらかじめ スレッド を必要数起動しておき, accept したらそのスレッドに処理を実施させる.

Evented (Reactor)

特徴

  • すべての処理をシングルスレッドで実施する.
  • マルチスレッドを利用しなくても, 非同期処理がかける.
  • スレッドやプロセス数の制限なしに, クライアントからの要求に答えることができる.

実装方法

  • ソケットを select で監視する. 読み取り可能, 書き込み可能なソケットのみに対して処理を実施する.
  • 遅い回線があるときに, その回線に引きずられて Reactor が他の処理をできない. すべての処理が Blocking される. そんなときは, non-blocking write を利用する.

sample

  • synchronized

    hosts.each do |host|
      sock = TCPSocket.new (host, 80)
      sock.write (request)
      sock.read
      sock.close
    end
  • threads

    threads = hosts.map do |host|
      Thread.new (host) do |h|
        sock = TCPSocket.new (h, 80)
        sock.write (request)
        sock.read
        sock.close
      end
    end
    threads.each{|t| t.join}
  • reactor

    write_socks = hosts.map do |host|
      TCPSocket.new (host, 80)
    end
    read_socks = []
     
    # handler
    write_proc = lambda{|sock|
      sock.write (request)
    }
     
    # handler
    read_proc = lambda{|sock|
      sock.read
      sock.close
    }
     
    # Reactor
    until (write_socks + read_socks).empty?
     
      # Demultiplexer
      r_socks, w_socks, e_socks = IO.select (read_socks, write_socks)
     
      # Dispatcher
      if ws = w_socks.first
        write_proc.call (ws)
        read_socks << ws
        write_socks.delete (ws)
      end
     
      # Dispatcher
      if rs = r_socks.first
        read_proc.call (rs)
        read_socks.delete (rs)
      end
    end