ACE(ADAPTIVE Communication Environment)はコンカレントなイベント駆動通信システムの開発を 簡略化する基本的なデザインパターンである。ACEはイベントの処理、イベントハンドラの起動、 コネクションの確立、ルーティング、アプリケーションサービスの動的コンフィグレーション、 並列性の制御といった通信ソフトウェア共通の処理を行なう多くのOSの上で動作可能な 再利用可能なC++ラッパー、フレームワークとクラスカテゴリの豊富なセットを提供する。
ACEの主な目的は、OSメカニズムの利用方法の単純化、プロセス間通信の提供、通信ポートの処理、 明示的な動的リンクと並列性である。加えてACEは、実行中のアプリケーションにサービスを動的に リンクし、それを複数のプロセスあるいはスレッドで実行させることで、通信ソフトのコンフィグ レーション・リコンフィグレーションを自動化する。
この論文ではACEの構造と機能、ACEの特徴を説明するためのいくつかの例を示す。ACEはフリー で提供され、以下のようなところで商用目的にに使われている。
また、同様に多くの研究機関や企業の研究プロジェクトでも使用されてる。ACEはSunOS 4.x,5x , SGI, HP-UX, OSF/1, AIXそしてWindowsNTなどの多くのシングルプロセッサあるいはマルチプロセッサのOSプラットフォームに移植されている。
障害に強く、高性能な分散通信システムソフトウェアシステムへの需要が近年劇的に増加している。こうしたシステム例としては、広域個人通信システム、通信管理のプラットフォーム、先進的な医療イメージシステムそしてリアルタイム市場データモニタ・分析システムがある。分散コンピューティングは接続性と内部動作(並列処理によるパフォーマンス、複製による信頼性と高可用性、モジュール化によるスケーラビリティとポータビリティ、動的構成と再構成による拡張性、資源の共有とオープンシステムによるコストパフォーマンス)を通し協調作業の完全を約束する技術である。
独立したコンピュータのネットワーク全体の分散サービスは多くの潜在的な利益を提供するが、分散コミュニケーションソフトウェアの開発はスタンドアロンアプリケーションに比べ複雑である。この複雑さの多くは、コミュニケーションソフトを開発するための使い古された道具と技術という制限から来ている。特に、多くの標準ネットワーク機構(sockets, TLI等)と再利用部品ライブラリ(X window, Sun RPC等)は型安全、移植性、リエントラント性、アプリケーションプログラミングインタフェース(API)の拡張性に欠ける。例えば、socketsとTLIは通信の両端を弱い型付けの整数のハンドルで識別しており、これは分かり難い実行時のエラーの可能性を増加させる。
開発を難しくしているもうひとつの要因は、アルゴリズムによる分割の普及である。多くの分散アプリケーションは、拡張性がないシステムアーキテクチャとよく言われるアルゴリズムによる分割技術を用いて開発されている。人気のあるネットワークプログラミングのテキストブックのソースコード例がアルゴリズム主導の設計・実装技術に基づいているということが、より問題を悪化させている。
オブジェクト指向技術はコミュニケーションシステムの開発の複雑さを減らすことを助ける多くの原理、方法、ツールを提供する。実際にどれだけオブジェクト指向技術がうまく適用できるかを説明するために、本論文ではADAOTIVE Communication Environment(ACE)の構造と機能を見ていく。ACEは基本的な通信ソフトウェアのデザインパターンに基づいた、無償のオブジェクト指向ネットワークプログラミングツールキットである。

ACEツールキットは低いレベルのC++ラッパー(OSの機構を隠蔽する)と高いレベルのクラスカテゴリとフレームワーク(これはC++ラッパーを拡張し、戦略的・戦術的な通信ソフトウェアデザインパターンを実装する)の両方を提供する層構造のアーキテクチャにより設計されている。
ACEはUNIXとWindowsNTの、以下のOSの並列性、プロセス間通信(IPC)、仮想メモリメカニズム(図1の一番下の部分)を隠蔽し拡張する、オブジェクト指向C++ラッパーを提供する。
アプリケーションは2章で説明するC++ラッパークラスライブラリを選択的に継承、集約、実体化し、これらの部品を組み合わせ構成する。C++ラッパーはC++の型安全のインタフェースでOSの通信、スレッド、仮想メモリ機構を隠蔽化することでアプリケーションの強度を高める。これによりアプリケーションが型安全ではないCライブラリに直接アクセスする量を減らすことができる。このためC++コンパイラは実行時ではなくコンパイル時に型不正を見つけることができる。ACEはC++ラッパーが提供する型安全と抽象化によるパフォーマンス低下を防ぐために、C++のinlineを使っている。
C++ラッパーに加え、ACEは低いレベルのC++ラッパーをてこ入れ拡張する高いレベルのフレームワークのコレクション(図1の上方)を提供する。フレームワークは関連するアプリケーション・ファミリーに再利用可能なアーキテクチャを形作る統合された部品の集合である。オブジェクト指向フレームワークは、グラフィカルユーザインタフェース、データベース、OSカーネル、通信サブシステムといった複雑なアプリケーション・ドメインで一般的になってきている。フレームワークは個々のクラスあるいは単独の関数の再利用にくらべ、ソフトウェアの大きな単位での再利用を可能にする。
ADAPTIVE Service eXecutive(ASX)フレームワーク(2.5章で説明)は複数の通信サービスからなる並列的な通信ソフトウェアアプリケーションの開発、構成、再構成を単純化・自動化する。ASXフレームワークは再利用可能なオブジェクト指向アーキテクチャにおいて、C++ラッパーに以下のものを提供できるようにする。
ASXは、テンプレート、継承、動的束縛等のC++言語の特徴とデザインパターンにより、変更・再コンパイル・再リンク・システムの再起動なしにアップデート・拡張できる通信ソフトウェアの開発を用意にする。
本論文は、2章でACE C++ラッパーと高レベルACEフレームワークを説明し、3章ではいくつかの製品アプリケーションのアーキテクチャの概要を、4章ではまとめを述べる。

ACEの最下層は、OSのマルチスレッド、ローカル・リモートプロセス間通信(IPC)、仮想メモリ機構を隠蔽するC++ラッパーから成る。C++ラッパーは強く、コンパクト、ポータブル、拡張性のある通信ソフトウェアの設計と実装を容易にする。ACEラッパーは図2に示すクラス分類に分割できる。それぞれのクラス分類は、インタフェースとサービスの一まとまりのセットを提供する。ACEの主要なクラス分類を次に示す。
本章ではこれらそれぞれのクラス分類を議論する。本論文を通しACEの部品をBooch法で表現する。実線の長方形は複数の関連するクラスを共通の名前空間にいれるクラス分類を示す。実線の雲型はオブジェクトを示し、ネストは一方のオブジェクトが他方の成分であることを示し、矢印ではない線は2つのオブジェクト間にある種の関係があることを示す。点線の雲型はクラスを示し、矢印はクラス間の継承関係を示し、一端に小さな円がある線は2つのクラス間の成分あるいは利用関係を示す。3角形の中の文字Aはクラスが抽象クラスであることを示す。
通信ソフトウェアは多くの種類のイベント(タイマーベース、I/Oベース、シグナルベース等)を非多重化し、処理する。例えば、inetdスーパーサーバは一連のI/O通信ポートをモニターするイベントループを内部に持っている。それぞれの通信ポートは、ネットワークサービス(ftp, telnet, time-of-day, echo他)を提供するアプリケーション特化のハンドラと対応付けられている。クライアントからの要求がモニタしているポート(標準のftpサービスはポート21)に到着すると、要求を実行するために関連付けられたサービスハンドラにディスパッチされる。(例:inetdはクライアントに代わってファイルを転送するためにftpプロセスをforkしexecする)
これらのイベント処理の動作を一つにまとめ自動化するために、ACEはReactorと呼ばれるイベントの非多重化とイベントハンドラへのディスパッチのフレームワークを提供する。ReactorはUNIXとWindowsNTのイベント非多重化機構(select, poll, WaitForMultipleObjects等)をポータブルで拡張可能なC++ラッパーの中に隠蔽化している。これらのOSのイベント非多重化システムコールは複数のI/Oハンドルで同時に発生する複数の異なるタイプの入出力イベントを検出する。
アプリケーションのポータビリティーのため、Reactorはどのイベント非多重化機構が使われているか気にしないですむ一つのインタフェースを提供している。それに加えて、Reactorはマルチスレッドイベント処理環境でコールバックスタイルのディスパッチを正確かつ効率的に行うための相互排他機構を提供している。

Reactorの中のオブジェクトの構造を図3に示す。これらのオブジェクトは(1)イベントの非多重化(タイマー起動の呼び出しキューにより生成される一時的なイベント、通信ポートに到着するI/Oイベント、シグナルイベント等)と(2)これらのイベントを処理するための、事前に登録されたハンドラの適当なメソッドへのディスパッチを行う責任を持つ。図3に示すように、全てのイベントハンドラオブジェクトは抽象ベースクラスEvent Handlerから導出される。このクラスはReactorがイベントの到着に対応して適当なアプリケーション特化のメソッドへディスパッチに使われるインタフェースを定義する。
Reactorは、I/Oハンドルベース、タイマーベース、シグナルベースのイベントを非多重化するために、Event Handlerインタフェースで宣言される仮想メソッドを用いる。I/Oハンドルベースのイベントはhandle_input, handle_output, handle_exceptionsメソッドを通しディスパッチされ、タイマーベースのイベントは、handle_timeoutメソッドによりディスパッチされ、シグナルベースのイベントはhandle_signalメソッドでディスパッチされる。
Event Handlerのサブクラス(3.1章で述べるLogging Handler等)はベースクラスのインタフェースをメソッドとデータメンバーを追加することで増やすことができる。加えて、Event Handlerのインタフェースの仮想メソッドはサブクラスでアプリケーション特化の機能を実装するために選択的に重複定義することができる。例えば3.2章のPBX監視サーバのアプリケーション特化のサブクラスはクライアントと通信するEvent Handlerオブジェクトを継承と(または)2.2章で説明するSOCK SAPまたはTLI SAP転送インタフェースクラスのオブジェクトの具体化することで定義している。Event Handlerベースクラスの仮想メソッドをサブクラスで定義した後、アプリケーションはその結果のイベントハンドラオブジェクトを具体化する。
次の例は2つのプロセスの間で双方向通信チャネルを利用しメッセージを相互に交換し続ける簡単なプログラムである。この例は、どのようにサービスがEvent Handlerから継承されるかを示している。また、どのようにReactorを非多重化とI/Oベース、シグナルベース、タイマーベースのイベントをディスパッチするために用いるかも示している。以下に示すPing PongクラスはEvent Hnadlerクラスからインタフェースを継承し、アプリケーション特化の機能を実装している。
class Ping_Pong : public Event_Hander
{
public:
Ping_Pong (char *b)
: len (min (strlen (b) + 1, BUFSIZ)) {
strncpy (this->buf, b, BUFSIZ);
}
virtual int handle_input (int fd) {
return read (fd, this->buf, BUFSIZ);
}
virtual int handle_output (int fd) {
return write (fd, this->buf, this->len);
}
virtual int handle_signal (int signum) {
this->finished = 1;
}
virtual int handle_timeout (const Time_Value &,
const void *) {
this->finished = 1;
}
bool done (void) {
return this->finished == 1;
}
private:
sig_atomic_t finished;
char buf[BUFSIZ]
size_t len;
};
双方向の通信チャネルはSVR4 UNIX STREAM パイプによりつくられる。
static int
init_fds (int fds[])
{
if (pipe (fds) == -1)
LM_ERROR ((LOG_ERROR, "%p\n%a", "pipe", 1));
// Enable message-oriented mode instead of
// bytestream mode.
int arg = RMSGN;
if (ioctl (fds[0], I_SRDOPT, arg) == -1
|| ioctl (fds[1], I_SRDOPT, arg) == -1)
return -1;
}
メインプログラムは適当な通信チャネルをオープンすることから開始される。続いてプログラムは子プロセスをフォークし、それぞれのプロセスでPing Pongイベントハンドラのオブジェクトをcallbackという名前で具体化し、Reactorのインスタンスに、I/Oベース、シグナルベース、タイマーベースのイベントにcallbackを登録し、イベントループに入る。
int main (int argc, char *argv[])
{
int fds[2];
Reactor reactor;
init_fds (fds);
pid_t pid = fork ();
Ping_Pong callback (argv[1]);
//Register I/O-based event handler
reactor.register_handler (
fds[pid == 0],
&callback,
Event_Handler::READ_MASK
| Event_Handler::WRITE_MASK);
//Register signal-based event handler
reactor.register_handler (SIGINT, &callback);
//Register timer-based event handler
reactor.schedule_timer (&callback, 0, 10);
/* Main event loop (run in each process) */
while (callback.done () == false)
reactor,handle_events ();
return 0;
}
タイマーベースとシグナルベースのイベントのcallbackイベントハンドラはReactorの中の適切なテーブルに保存される。 同様にReactorは、register_handlerがI/Oベースのイベントハンドラを登録するために実行された時、適切なハンドラを内部テーブルに保存する。アプリケーションが続いてメインのイベントループReactor::handle_eventsを実行すると、このハンドルは内部でOSのI/O非多重化のシステムコール(select, poll等)への引数として渡される。
実行時に予めイベントハンドラcallbackオブジェクトにて登録されたものに対応した、入出力、シグナル、タイマーイベントが発生すると、Reactorは自動的にイベントをイベントハンドラオブジェクトの適当なメソッドにディスパッチする。callbackオブジェクトのディスパッチされたメソッドは、アプリケーションに応じた機能(例:通信チャネルへのメッセージの書き込み、メッセージの読み込み、プログラムの終了させるためのフラグをセットする。)を実行しなければならない。これらの部品の相互動作関係を図4のオブジェクト関係図(object interaction diagram)に示す。

ACEはIPC_SAP(InterProcess Communication Service Access Point)基本クラスを基とした、多くのクラスカテゴリを提供している。IPC_SAPは、コネクション指向とコネクションなしのプロトコルの、標準的な入出力ベースのOSのローカルとリモートのIPC機構を隠蔽化している。図5のように、これらのクラスカテゴリには、SOCK SAP(socket APIを隠蔽化), TLI SAP(TLI API), SPIPE SAP(UNIX SunOS 5.x STREAMパイプAPI), FIFO SAP(UNIX named pipe API)を含む。

それぞれのクラスカテゴリは継承の階層になっている。全てのサブクラスは、ローカルあるいはリモート通信機構のサブセットに、よく定義されたインタフェースを提供している。同時に、階層中のサブクラスは個々の通信の枠組み(communication abstraction)(インターネットドメインあるいはUNIXドメイン)全体の機能を構成している。単独の関すを利用するのに比べてクラスを使用することは、次のようにネットワークプログラミングを容易にする。
以下の章ではIPC SAPの個々のクラスカテゴリについて議論する。

SOCK SAP C++クラスカテゴリはアプリケーションにインターネットドメインとUNIXドメインプロトコルファミリのオブジェクト指向インタフェースを提供する。アプリケーションは基礎にあるインターネットドメインとUNIXドメインsocketタイプの機能を、図6に示す適切なSOCK SAPのサブクラスを継承あるいは具体化しアクセスする。SOCK*サブクラスはインターネットドメインの機能を隠蔽化し、LSOCK*サブクラスはUNIXドメインの機能を隠蔽化する。図6に示すように、サブクラスはさらに、(1)*Dgramコンポーネント(信頼性のない、非接続型のメッセージ指向の機能を提供する)vs. Streamコンポーネント(信頼性のあるコネクション指向のバイトストリーム機能を提供する)と(2)*Acceptorコンポーネント(サーバがよく使う、コネクションの確立機能を提供する)vs. *Streamコンポーネント(クライアントとサーバの両方で使われる、双方向の倍とストリームデータ転送機能を提供する)に分解される。
C++ラッパーを使ってsocketインタフェースを隠蔽化することは次の3点のメリットがある。 (1)コンパイル時に、捕らえ難いアプリケーションの型違反を検出する。(2)アプリケーションの移植性を増す、プラットフォームに依存しない転送レベルインタフェースを提供しする。(3)低レベルのネットワークプログラミングの細かい点に費やしていた労力を大きく減少させる。後者のポイントを説明するために、次のプログラミング例で、LANのサブネット上で、指定されたポート番号を見ている全てのサーバにメッセージをブロードキャストするために、SOCK Dgram Bcastクラスを使用するクライアントアプリケーションを実装している。
int
main (int argc, char *argv[])
{
SOCK_Dgram_Bcast bsap (sap_any);
char *msg;
unsigned short b_port;
msg = argc > 1 ? argv[1] : "hello world\n";
b_port = argc > 2 ? atoi (argv[2]) : 12345;
if (b_sap.send (msg, strlen (msg),
b_port) == -1)
perror ("can't send broadcast"), exit (1);
exit (0);
}
socketインタフェースを直接使って作成する多くの行数のコードとこれを比較してみるとよい。
TLI SAP クラスカテゴリは、SYSTEM VのTransport Layer Interface(TLI)の C++のインタフェースを提供する。TLI SAPの継承階層は、ソケットのSOCK SAP C++ ラッパーとほぼ同じである。一番の違いはTLIとTLI SAPがUNIXドメインのプロトコル ファミリのインタフェースを定義しない点である。C++の特徴(デフォールトパラメータ やテンプレート)とtirdwr(STREAMSモジュールのリード/ライト コンパチビリティ) が一緒になることで、ソケットベースあるいはTLIベースの転送インタフェースの どちらでも正しく扱えるように、コンパイル時にパラメタライズされた アプリケーションを開発することが簡単になっている。
次のコードは、アプリケーションがどのようにC++テンプレートを使って IPC機構をパラメタライズするかを示している。このコードは3.1章で説明する 分散ロギング機能から抜出したものである。以下のコードではEvent Handlerから 派生したサブクラスが転送I/Fとプロトコルアドレスクラスでパラメータ化されて いる。
/* Logging_IO header file */ templateclass Logging_IO : public Event_Handler { public: Logging_IO (void); virtual ~Logging_IO (void); virtual int handle_close (HANDLE); virtual int handle_input (HANDLE); virtual HANDLE get_handle (void) const { return this->peer_stream_.get_handle (); } protected: PEER_STREAM peer_stream_; };
OSの特性(BSDベースのSunOS 4.xかSYSTEM VベースのSunOS 5.x)によって、 ロギングアプリケーションはClient Handlerクラスを以下に示すようにSOCK SAP あるいはTLI SAPを用いて具体化する。
/* Logging application */ class Logging_IO #if defined (MT_SAFE_SOCKETS) : public Logging_IO#else : public Logging_IO #endif /* MT_SAFE_SOCKETS */ { /* ... */ };
このテンプレートによるフレキシビリティの増加は、複数のOSの上で動作可能な アプリケーションを開発する上で、非常に便利である。特に、転送インタフェース によりアプリケーションをパラメタライズすることが可能なことは、スレッドセーフ ではないSun0S 5.2のsocketの実装やSunOS 4.xの不具合の多いTLIの実装といった、 様々なSunOSにまたがるものを作る場合必要である。
TLI SAPはまた、TLIインタフェースの多くの詳細からアプリケーションを 隔離する。例えば、qlen > 1の並列サーバの、不明確で間違いやすい動作をする t_listenとt_acceptを使わなければならない微妙なアプリケーションレベルのコード は、TLI Acceptorクラスのacceptメソッドに隠蔽化される。このメソッドは、 クライアントからの接続要求を受付ける。C++のデフォルト引数によって、 acceptメソッドを呼出す標準の方法はTLI_SAPベースでもSOCK SAPベースでも 構文的には同じである。
SPIPE SAPクラスカテゴリは、マウントされたSTREAMパイプをconnldのC++ ラッパーインタフェースを提供する。SunOS 5.xは、パイプハンドルを UNIXファイルシステムの指定された場所にマウントするfattachシステムコールを 提供する。サーバのアプリケーションは、パイプのマウントされた終端に、 connld STREAMモジュールを入れることで作られる。サーバと同じホストマシンで 動いているクライアントアプリケーションが正しい順序でマウントされたパイプ に結びつけられたファイル名をオープンすると、クライアントとサーバは 非多重・双方向の通信チャネルのハンドルをそれぞれ得る。
SPIPE SAPの継承階層は、SOCK SAPやTLI SAPのものと同じである。 それはSOCK SAP LSOCK* クラス(それ自身がUNIXドメインsocketsを隠蔽する)と 類似の機能を提供する。しかしながら、SPIPE SAPはSTREAMモジュールを SPIPE SAPの終端でPush,Pop可能にするため、よりフレキシブルと言える。 SPIPE SAPはまた、同一ホスト内でのプロセス間、スレッド間のバイトストリーム と優先度付メッセージ指向のデータの双方向の配送をサポートする。
FIFO SAPクラスカテゴリは、UNIX名前付パイプ機構(FIFOとも呼ばれる)を隠蔽化 する。 STREAMパイプと異なり名前付パイプは、1つあるいは複数の送信元から1つの受信者 への単方向通信だけを提供している。さらに異なる送信者からのメッセージもすべて 同じ通信チャネルに入る。このため、受信側がメッセージの送信元を特定するための、 ある種の非多重化識別子をそれぞれのメッセージに明示的に入れる必要がある。 SunOS 5.xのSTREAMSベースの名前付パイプの実装は、メッセージ指向とバイトストリーム指向のデータ配送の両方の仕組を提供する。対照的にSunOS 4.xはバイトストリーム 指向の名前付パイプだけを提供する。従って、固定長メッセージが常に使われる場合 を除き、SunOS 4.xで名前付パイプで送られる個々のメッセージは、受信側が 通信チャネルのバイトストリームからメッセージを取出せるように、何らかの バイト数か、特別な終了シンボルによって区別する必要がある。この制限を軽減する ために、SunOS 4.x FIFO SAPの実装はSunOS 5.xで可能なメッセージ指向の仕組を エミュレートするロジックを含んでいる。
sockets,TLIといった、ハンドルベースI/O通信機構の隠蔽化に加え、ACEは SYSTEM V UNIX IPCメカニズムとメモリマップドファイルのC++ラッパーを提供する。
SunOS UNIXは"SYSTEM V IPC" として知られる、シェアードメモリ,同期,メッセージ 交換機構一式を提供する。これらの機構により提供される大部分の機能は、 最近のSunOS UNIXで含まれた(mmap,スレッド同期,STREAMパイププリミティブ等)。 しかしながら、ある種のアプリケーション(データベースエンジン等)は、SYSTEM V IPC機構の恩恵(peer-to-peerメッセージキュー、効果的な多重処理を行うための原始的な 機構であるセマフォ、SYSTEM V IPCのUNIXプラットフォームへの広がり)を受けている。 しかしSYSTEM V IPC機構(特にセマフォ)を正しく理解し使うことは、それらの インタフェースが非常に汎用的で、その動作が最近まで体系的に文書化されなかった ため、なかなか困難である。
ACEのSYSTEM V IPC C++ラッパーは無数の不必要な詳細から開発者を守る。 例えばACE C++ラッパーのSYSTEM V IPCセマフォーは、標準的なwaitとsignalセマフォ操作を利用するアプリケーションの作成を、より直感的で単純にする。以下に典型的な生産者と消費者の例のコードの断片を示す。
typedef SV_Semaphore_Simple SEMA;
SEMA prod (1, SEMA::CREATE, 1);
SEMA cons (2, SEMA::CREATE, 0);
void producer (void)
{
for (;;) {
prod.wait ();
// produce resource...
cons.signal ();
}
}
void consumer (void)
{
for (;;) {
cons.wait ();
// consume resource...
prod.signal();
}
}
簡潔なC++ラッパーのインタフェースは、SYSTEM V セマフォを直接使うのに必要な 冗長なCのインタフェースに比べ、直感的である。
Mem Mapクラスは、メモリマップドファイルシステムコールのmmapファミリへのC++ インタフェースを提供する。これらの呼出しは、ファイルをプロセスのアドレス スペ−ス内にマップするために、下層のOSの仮想メモリ機能を利用する。 マップドファイルの中身はポインタを通して直接アクセスできる。ポインタ インタフェースは、間接的な標準的なread/write IOシステムコールによるブロック 単位のデータアクセスより効率的である。加えて、メモリマップドファイルの中身は 2つ以上のプロセスの間で簡単に共有できる。
mmapインターフェースはちょっと変っているが、それは開発者にマニュアルで 細かい操作(ファイルを明示的にオープンし、その長さを決定する等)を要求する。 対照的に、Mem Map C++ラッパーは、典型的なmmap利用パターンを単純かするために、 デフォルトの引数と幾つか引数パターンのコンストラクタを提供する。
例えば次のプログラムは、コマンドラインの引数で渡されたファイルをマップし その行を反対に印字するのにMem map C++ラッパーを使用している。
static void
putline (const char *s)
{
while (putchar (*s++) != '\n')
continue;
}
int
main (int argc, char *argv[])
{
char *filename = argv[1];
char *file_p;
Mem_Map mmap (filename);
if (mmap (file_p) != -1)
{
size_t size = mmap.size () - 1;
if (file_p[size] == '\0')
file_p[size] = '\n';
while (--size >= 0)
if (file_p[size] == '\n')
putline (file_p + size + 1);
putline (file_p);
return 0;
}
else
return 1;
}
このC++ラッパーのインタフェースの使われかたと、readのようなI/Oシステム コールを直接使う場合に必要となる冗長なCのインタフェースを比較してみるとよい。
class Resource_Manager
{
public:
Resource_Manager (u_int initial_resources)
: resource_add_ (this->lock),
resource_ (initial_resources) {}
int acquire_resources (u_int, amount_wanted)
{
this->lock_.acquire ();
while (this->resources_ < amount_wanted) {
this->waiting_++;
/* Block until resources are released */
this->resource_add_.wait ();
}
this->resource_ -= amount_wanted;
this->lock_.release ();
}
int release_resources (u_int amount_released)
{
this->lock_.acquire ();
this->resources_ += amount_released;
if (this->waiting_ == 1) {
this->waiting_ = 0;
this->resource_add_.signal ();
}
else if (this->waiting_ > 1) {
this_waiting_ = 0;
this->resource_add_.broadcast ();
}
this->lock_.release ();
}
// ...
private:
Mutex lock_;
Condition resource_add_;
u_int resources_;
u_int waiting_;
// ...
};
どのようにConditionのオブジェクトresource_add_のコンストラクタが、 がMutexのオブジェクトlockとCondition_オブジェクトを結びつけるかに注意が 必要である。これは、SunOSのcond_tmcond_waitに比べ、Condition::waitコールの インタフェースを単純化している。
さらに、Mutexラッパーはreleaseを呼び忘れる(プログラマの怠慢やC++の 例外処理による)という潜在的に間違いやすい、複数のスレッドの制御の同期に、 よりエレガントな方法を提供している。より強いアプリケーションのため、ACEの 同期機能はC++のクラスコンストラクタとデストラクタの仕組を強化している。 Mutexロックが自動的に要求され・解放されることを確かにするために、 ACEはGuardというヘルパークラスを提供する。以下にその定義を示す。
templateclass Guard { public: Guard (Mutex &m): lock (m) { this->lock_.acquire (); } ~Guard (void) { this->lock_.release (); } private: MUTEX &lock_; }
GuardのオブジェクトはMutexを要求するコードの固まりを定義し、その固まりから 抜出る時には自動的に解放する。
Guardクラスは相互排他機能によりパラメタライズされたテンプレートとして定義 されていることに注意が必要。いくつかの異なるタイプのmutexの方法がある。 それぞれの相互排他機能は同じインタフェース(acquire/release)を共有するが、 異なるシリアライゼーションと性能の特質を持つ。ACEは、非リカーシブとリカーシブ の2つのタイプの相互排他をサポートしている。
非リカーシブロックは、同時に1つのスレッドしか動作してはならない クリティカルセクションを定義するのに効果的な相互排他機能を提供する。 非リカーシブなので、lockを行っているスレッドはそれを解放せずに次のlockを 要求することはできない。そうでないとすぐにデッドロックが発生する。 Sun0S 5.xは、mutex_t, rwlock_t, sema_t(POSIX Pthreadsは後の2つの機構を サポートしていない。)の非リカーシブロックを提供している。ASXフレームワークは Mutex, RW_Mutex, Semaphore C++ラッパーをそれぞれを隠蔽化するために提供する。
一方リカーシブロックは、スレッドそれ自身がlockを持っている間は、何度でも lockを要求することができる。リカーシブロックは、特に、フレームワークの イベントループがあらかじめ登録されているユーザ定義のオブジェクトをコールバック する、コールバック方式のイベントディスパッチフレームワーク(2.1章のReactor)に おいて、便利である。ユーザ定義のオブジェクトはそのメソッドのエントリポイント を通してそのディスパッチフレームワークに連続して再入することがあり、リカーシ ブロックは、コールバック中のフレームワークがlockを持った状態でのデッドロック を防ぐために必要である。
つぎのC++テンプレートクラスは、本来リカーシブロックをサポートしない Solaris threadsとPOSIX Pthreadsでの同期機構のためのリカーシブロックを実装 している。
templateclass Recursive_Lock { public: Recursive_Lock (void) : nesting_level_ (0), thr_id_ (0) {} int acquire (void) const { thread_t t_id = thr_self (); // Check if we already hold the lock if (t_id == this->thr_id_) { ++this->nesting_level_; return 0; } else { int result = this->lock_.acquire (); int (result == 0) { this->thr_id_ = t_id; this->nesting_level_ = 0; } return result; } } int release (void) const { if (this->nesting_level_ > 0) { --this->nesting_level_; return 0; } else { this->thr_id_ = 0; return this->lock_.release (); } } private: MUTEX lock_; thread_t thr_id_; mutable int nesting_level_; };
Recutsive Lockは適切なacquire/releaseインタフェースを提供するどのACEラッパー クラスとでも一緒に、具体化できる。これは全てのACEのロックインタフェースを含む。
次のコードはReactorでのGuardとRecursive Lockの使い方である。
int
Reactor::register_handler (const Event_Handler *eh,
Reactor_Mask mask)
{
/* Constructor acquires the lock on entry */
Guard< Recursive_Lock >m (this->lock_);
int h = h->get_handle ();
if (h >= 0 && h < this->max_size)
return this->attach (eh, mask, h);
else
return -1;
/* Destructor of Guard releases the lock on exit */
}
このコードはEvent Handlerのオブジェクトの登録をクリティカルセクションとして 実行している。この例はまた、Guardオブジェクトが作らる時、自動的に 同期オブジェクトのロックを要求するクラスのコンストラクタのC++の イディオムを示している。同様にクラスのデストラクションは、monオブジェクトが スコープの外に出た時自動的にオブジェクトをアンロックする。 さらに、monのデストラクタは前述のメソッドのif/elseステートメントのどちらの returnであっても、自動的にMutexロックをリリースする。さらに加えて、 register_handlerメソッドの処理中にC++の例外が発生した場合も、ロックが自動的に リリースされる。 Recursive Lock mutexはReactorによってディスパッチされたアプリケーションの指定 したコールバックがデッドロックを起こさないようにするために使用される。
ACEはまた、機能を実現するために協調して動作するスレッド群を管理する機構を持つ Thread Managerクラスを提供する。Thread Managerクラスは、複数のスレッドを サスペンド・レジュームする仕組み(suspenf_add, resume_all)を提供する。