Communicating Sequential Processes

Communicating Sequential Processes（CSP）とは、並行性に関するプロセス計算の理論のひとつである。プログラミング言語Occamにも影響を与えた。 CSPは1978年、アントニー・ホーアが最初に考案し、その後かなり改良されていった。CSPは様々なシステムにおける並行性を記述し検証する、形式仕様記述ツールとして産業で利用されてきた。たとえば、T9000トランスピュータやセキュアな電子商取引システムなどの例がある。理論としても、応用範囲を広げる（より大規模なシステムの解析に使えるようにする）などの研究が行われている。 ホーアの1978年の論文で提示されたCSPは、プロセス計算というよりも本質的には並行プログラミング言語であった。後のCSPとは構文が著しく異なり、数学的に定義された意味論を持っておらず、無制限の非決定性を表現することはできなかった。本来のCSPでは、並列に動作する有限個の逐次プロセスが同期式のメッセージパッシングで相互に通信するという形式でプログラムを記述していた。その後のCSPとは対照的に、各プロセスには名前が付けられ、メッセージには送信元と送信先の名前が指定されている。たとえば次のプロセス COPY = *[c:character; west?c → east!c] は、west という名前のプロセスから文字を繰り返し受信し、その文字を east という名前のプロセスに送信する。並列合成 [west::DISASSEMBLE || X::COPY || east::ASSEMBLE] では、west という名前を DISASSEMBLE プロセスに割り当て、X という名前を COPY プロセスに割り当て、east という名前を ASSEMBLE プロセスに割り当て、これらを並行に実行する。 その後、ホーアは Stephen Brookes や Bill Roscoe らと共に理論面の改良に取り組み、CSPをプロセス代数的にしていった。この方向性は、同時期にロビン・ミルナーが行っていた Calculus of Communicating Systems（CCS）の研究と相互に影響しあっている。CSPの理論面は1984年に発表され、1985年に出版されたホーアの著書 Communicating Sequential Processes で一般に知られるようになった。2006年9月現在でも、Citeseer によればこの本は計算機科学分野での引用回数第3位とされている（ただし、サンプリング方式であるため信頼性は高くない）。このホーアの著書以降、CSPの理論は若干変更されただけである。変更のほとんどは、CSPプロセス解析と検証のための自動化ツールの出現に対応するものである。Roscoe の The Theory and Practice of Concurrency ではこの新たなCSPが解説されている。 名前が示すとおり、CSPは独立したプロセス群がメッセージパッシングによって通信することで相互にやり取りしているものとしてシステムを記述する。しかし、CSPの名称に含まれる "Sequential"（逐次的）という部分は誤解を生じる可能性がある。というのも最近のCSPでは、プロセスは単なる逐次的プロセスだけでなく、より基本的なプロセス群の並列合成で生成されるプロセスも含まれるからである。プロセス間の関係やプロセスが周囲と通信する方法は、各種プロセス代数演算子を使って表される。このような代数的手法を使うことで、少数のプリミティブ要素から容易に極めて複雑なプロセスを構築できる。 CSP は、そのプロセス代数における2種類のプリミティブのクラスを提供する。 イベント イベントとは、通信あるいは相互作用を意味する。不可分で瞬間的なものと見なされる。不可分名（たとえば、 o n {\displaystyle {\mathit {on}}} 、 o f f {\displaystyle {\mathit {off}}} ）、複合名（たとえば、 v a l v e . o p e n {\displaystyle valve.open} 、 v a l v e . c l o s e {\displaystyle valve.close} ）、入出力イベント（たとえば、 m o u s e ? x y {\displaystyle mouse?xy} 、 s c r e e n ! b i t m a p {\displaystyle screen!bitmap} ）などがある。 プリミティブプロセス プリミティブプロセスとは、基本的振る舞いを表したものである。たとえば、 S T O P {\displaystyle {\mathit {STOP}}} （まったく通信しないプロセスであり、デッドロックとも言う）や S K I P {\displaystyle {\mathit {SKIP}}} （成功裡に終了することを表す）がある。 CSP には様々な代数演算子がある。主なものを以下に挙げる。 プレフィックス プレフィックス演算子は、イベントとプロセスを結合して新たなプロセスを生成する。たとえば、 a → P {\displaystyle a\rightarrow P} は、 a {\displaystyle {\mathit {a}}} にその環境と通信させようとするプロセスであり、 a {\displaystyle {\mathit {a}}} の後では、プロセス P {\displaystyle {\mathit {P}}} として振舞う。 決定的選択 決定的（または外部）選択演算子は、プロセスの将来を2つのコンポーネントプロセス間の選択で定義可能とするもので、プロセス群と初期イベントとのやり取りによって環境が選択を行うことを可能にする。たとえば ( a → P ) ◻ ( b → Q ) {\displaystyle \left(a\rightarrow P\right)\Box \left(b\rightarrow Q\right)} は、初期イベント a {\displaystyle {\mathit {a}}} および b {\displaystyle {\mathit {b}}} とやり取りしようとするプロセスであり、環境がどちらの初期イベントとやり取りすることを選択したかによって、 P {\displaystyle {\mathit {P}}} または Q {\displaystyle {\mathit {Q}}} として振舞うようになる。 a {\displaystyle {\mathit {a}}} および b {\displaystyle {\mathit {b}}} と同時にやり取りする場合、選択は非決定的となる。 非決定的選択 非決定的（または内部）選択演算子は、プロセスの将来を2つのコンポーネントプロセス間の選択で定義可能とするものだが、環境がどちらのコンポーネントプロセスを選択するかを制御できない。たとえば ( a → P ) ⊓ ( b → Q ) {\displaystyle \left(a\rightarrow P\right)\sqcap \left(b\rightarrow Q\right)} は、 ( a → P ) {\displaystyle \left(a\rightarrow P\right)} または ( b → Q ) {\displaystyle \left(b\rightarrow Q\right)} のどちらかとして振舞う。 a {\displaystyle {\mathit {a}}} または b {\displaystyle {\mathit {b}}} を受容することを拒否でき、環境が a {\displaystyle {\mathit {a}}} と b {\displaystyle {\mathit {b}}} の両方を提供する場合にそれとやり取りするだけである。決定的選択で選択肢である2つの初期イベントが同一である場合、非決定性が生じる。したがってたとえば、 ( a → a → S T O P ) ◻ ( a → b → S T O P ) {\displaystyle \left(a\rightarrow a\rightarrow STOP\right)\Box \left(a\rightarrow b\rightarrow STOP\right)} は次と等価である。 a → ( ( a → S T O P ) ⊓ ( b → S T O P ) ) {\displaystyle a\rightarrow \left(\left(a\rightarrow STOP\right)\sqcap \left(b\rightarrow STOP\right)\right)} インターリーブ インターリーブ演算子は、完全に独立した並行動作を表す。次のプロセス P | | | Q {\displaystyle P\;\vert \vert \vert \;Q} は、 P {\displaystyle {\mathit {P}}} と Q {\displaystyle {\mathit {Q}}} が同時並行に動作することを意味する。両方のプロセスが発生するイベントは時系列上でインターリーブされる。 インタフェース並列 インタフェース並列演算子は、コンポーネントプロセス間で同期を必要とする並行動作を表す。インタフェースにおけるイベントは、全コンポーネントプロセスがそのイベントに関わることが可能な場合にのみ発生する。たとえば、次のプロセス P | [ { a } ] | Q {\displaystyle P\left\vert \left[\left\{a\right\}\right]\right\vert Q} は、イベント a {\displaystyle {\mathit {a}}} が発生する前に P {\displaystyle {\mathit {P}}} と Q {\displaystyle {\mathit {Q}}} が共にそのイベントを扱える状態になっている必要があることを意味する。したがって例えば、次のプロセス ( a → P ) | [ { a } ] | ( a → Q ) {\displaystyle \left(a\rightarrow P\right)\left\vert \left[\left\{a\right\}\right]\right\vert \left(a\rightarrow Q\right)} は、イベント a {\displaystyle {\mathit {a}}} を扱うことができ、次のように表せる。 P | [ { a } ] | Q {\displaystyle P\left\vert \left[\left\{a\right\}\right]\right\vert Q} 一方 ( a → P ) | [ { a , b } ] | ( b → Q ) {\displaystyle \left(a\rightarrow P\right)\left\vert \left[\left\{a,b\right\}\right]\right\vert \left(b\rightarrow Q\right)} は、単純なデッドロックを意味する。 隠蔽 隠蔽演算子は、何らかのイベントを観測不可能とすることでプロセスの抽象化手段を提供する。隠蔽の例として次の ( a → P ) ∖ { a } {\displaystyle \left(a\rightarrow P\right)\setminus \left\{a\right\}} は、イベント a {\displaystyle {\mathit {a}}} が P {\displaystyle {\mathit {P}}} の中で出現しないものとすると、以下のように省略可能である。 P {\displaystyle {\mathit {P}}} 簡単なCSPの例として、チョコレートの自動販売機の抽象表現とチョコレートを購入しようとしている人との相互作用を考える。この自動販売機は2つのイベント "coin" と "choc" を扱う。"coin" は代金の投入を表し、"choc" はチョコレートの引渡しを表す。チョコレートを渡す前に代金の支払いを要求する機械は次のように記述される。 V e n d i n g M a c h i n e = c o i n → c h o c → S T O P {\displaystyle VendingMachine=coin\rightarrow choc\rightarrow STOP} 支払いに硬貨またはカードを使う人は、以下のようにモデル化される。 P e r s o n = ( c o i n → S T O P ) ◻ ( c a r d → S T O P ) {\displaystyle Person=(coin\rightarrow STOP)\Box (card\rightarrow STOP)} これら2つのプロセスを並列に置くことで、互いにやり取りできるようにする。合成プロセスの振る舞いは、2つのプロセスが同期しなければならないイベント依存する。すなわち、 V e n d i n g M a c h i n e | [ { c o i n , c a r d } ] | P e r s o n ≡ c o i n → c h o c → S T O P {\displaystyle VendingMachine\left\vert \left[\left\{coin,card\right\}\right]\right\vert Person\equiv coin\rightarrow choc\rightarrow STOP} ここで、同期が "coin" についてのみ必要とされる場合、以下が得られる。 V e n d i n g M a c h i n e | [ { c o i n } ] | P e r s o n ≡ ( c o i n → c h o c → S T O P ) ◻ ( c a r d → S T O P ) {\displaystyle VendingMachine\left\vert \left[\left\{coin\right\}\right]\right\vert Person\equiv \left(coin\rightarrow choc\rightarrow STOP\right)\Box \left(card\rightarrow STOP\right)} この合成プロセスを "coin" と "card" というイベントを隠蔽することで抽象化すると、次のようになる。 ( ( c o i n → c h o c → S T O P ) ◻ ( c a r d → S T O P ) ) ∖ { c o i n , c a r d } {\displaystyle \left(\left(coin\rightarrow choc\rightarrow STOP\right)\Box \left(card\rightarrow STOP\right)\right)\setminus \left\{coin,card\right\}} 以上から、次の非決定性プロセスが得られる。 ( c h o c → S T O P ) ⊓ S T O P {\displaystyle \left(choc\rightarrow STOP\right)\sqcap STOP} これは、"choc" イベント後に停止するか、単に停止するプロセスである。言い換えれば、システムを外部から見たものとして上記抽象化を扱えば（すなわち、人間が行った決定を無視すれば）、非決定性が生じる。 CSPの構文は、プロセスとイベントの結合の「正当な」方法を定義する。 e {\displaystyle {\mathit {e}}} をイベント、 X {\displaystyle {\mathit {X}}} をイベントの集合とする。するとCSPの基本構文は以下のように定義される。 P r o c ::= S T O P | S K I P | e → P r o c ( prefixing ) | P r o c ◻ P r o c ( external choice ) | P r o c ⊓ P r o c ( nondeterministic choice ) | P r o c | | | P r o c ( interleaving ) | P r o c | [ { X } ] | P r o c ( interface parallel ) | P r o c ∖ X ( hiding ) | P r o c ; P r o c ( sequential composition ) | i f b t h e n P r o c e l s e P r o c ( boolean conditional ) | P r o c ▹ P r o c ( timeout ) | P r o c △ P r o c ( interrupt ) {\displaystyle {\begin{array}{lcll}{Proc}&::=&\mathrm {STOP} &\;\\&|&\mathrm {SKIP} &\;\\&|&e\rightarrow {Proc}&({\text{prefixing}})\\&|&{Proc}\;\Box \;{Proc}&({\text{external}}\;{\text{choice}})\\&|&{Proc}\;\sqcap \;{Proc}&({\text{nondeterministic}}\;{\text{choice}})\\&|&{Proc}\;\vert \vert \vert \;{Proc}&({\text{interleaving}})\\&|&{Proc}\;|[\{X\}]|\;{Proc}&({\text{interface}}\;{\text{parallel}})\\&|&{Proc}\setminus X&({\text{hiding}})\\&|&{Proc}\;;{Proc}&({\text{sequential}}\;{\text{composition}})\\&|&\mathrm {if} \;b\;\mathrm {then} \;{Proc}\;\mathrm {else} \;Proc&({\text{boolean}}\;{\text{conditional}})\\&|&{Proc}\;\triangleright \;{Proc}&({\text{timeout}})\\&|&{Proc}\;\triangle \;{Proc}&({\text{interrupt}})\end{array}}} 註：上記の構文では簡潔にするため、発散 (計算機科学)（英語版）を表す d i v {\displaystyle \mathbf {div} } プロセスや、アルファベット付き並列、パイピング、インデックス付き選択などの様々な演算子を省略している。 文法的に正しいCSPの表現の意味を定義する形式意味論はいくつかある。CSPの理論には、相互に一貫した表示的意味論、代数的意味論、操作的意味論がある。 CSPの主な表示的モデルとして、トレースモデル、安定失敗モデル、失敗/発散モデルの3つがある。プロセス表現とこれらモデルとの意味論的マッピングがCSPの表示的意味論となる。 トレースモデルは、そのプロセスが扱う一連のイベント（トレース）でプロセス表現の意味を定義する。例えば、 t r a c e s ( S T O P ) = { ⟨ ⟩ } {\displaystyle traces\left(STOP\right)=\left\{\langle \rangle \right\}} 何故なら S T O P {\displaystyle STOP} は何のイベントも扱わないから t r a c e s ( a → b → S T O P ) = { ⟨ ⟩ , ⟨ a ⟩ , ⟨ a , b ⟩ } {\displaystyle traces\left(a\rightarrow b\rightarrow STOP\right)=\left\{\langle \rangle ,\langle a\rangle ,\langle a,b\rangle \right\}} 何故ならプロセス ( a → b → S T O P ) {\displaystyle (a\rightarrow b\rightarrow STOP)} は、何のイベントも扱わない場合、イベント a {\displaystyle a} を受け付ける場合、イベント a {\displaystyle a} を受け付けた後でイベント b {\displaystyle b} を受け付ける場合の3つのケースがありうるから より形式的に表せば、プロセス P {\displaystyle P} のトレースモデルでの意味は t r a c e s ( P ) ⊆ Σ ∗ {\displaystyle traces\left(P\right)\subseteq \Sigma ^{\ast }} として定義される。すなわち、 ⟨ ⟩ ∈ t r a c e s ( P ) {\displaystyle \langle \rangle \in traces\left(P\right)} （ t r a c e s ( P ) {\displaystyle traces\left(P\right)} は空のシーケンスを含む） s 1 ⌢ s 2 ∈ t r a c e s ( P ) ⟹ s 1 ∈ t r a c e s ( P ) {\displaystyle s_{1}\smallfrown s_{2}\in traces\left(P\right)\implies s_{1}\in traces\left(P\right)} （ t r a c e s ( P ) {\displaystyle traces\left(P\right)} はプレフィックスで閉じている） ここで、 Σ ∗ {\displaystyle \Sigma ^{\ast }} は考えられる全てのイベントの有限な並びの集合である。 安定失敗モデルはトレースモデルを拒絶集合（refusal set）で拡張したものである。拒絶集合とは、プロセスが実行を拒絶できるイベントの集合 X ⊆ Σ {\displaystyle X\subseteq \Sigma } である。失敗（failure）は ( s , X ) {\displaystyle \left(s,X\right)} という対で表される。ここで s {\displaystyle s} はトレース、 X {\displaystyle X} は拒絶集合であり、トレース s {\displaystyle s} が実行されたときそのプロセスが拒絶するイベント群を表す。安定失敗モデルにおけるプロセスの観測された振る舞いは、 ( t r a c e s ( P ) , f a i l u r e s ( P ) ) {\displaystyle \left(traces\left(P\right),failures\left(P\right)\right)} という対で表される。例えば、 f a i l u r e s ( ( a → S T O P ) ◻ ( b → S T O P ) ) = { ( ⟨ ⟩ , ∅ ) , ( ⟨ a ⟩ , { a , b } ) , ( ⟨ b ⟩ , { a , b } ) } {\displaystyle failures\left(\left(a\rightarrow STOP\right)\Box \left(b\rightarrow STOP\right)\right)=\left\{\left(\langle \rangle ,\emptyset \right),\left(\langle a\rangle ,\left\{a,b\right\}\right),\left(\langle b\rangle ,\left\{a,b\right\}\right)\right\}} f a i l u r e s ( ( a → S T O P ) ⊓ ( b → S T O P ) ) = { ( ⟨ ⟩ , { a } ) , ( ⟨ ⟩ , { b } ) , ( ⟨ a ⟩ , { a , b } ) , ( ⟨ b ⟩ , { a , b } ) } {\displaystyle failures\left(\left(a\rightarrow STOP\right)\sqcap \left(b\rightarrow STOP\right)\right)=\left\{\left(\langle \rangle ,\left\{a\right\}\right),\left(\langle \rangle ,\left\{b\right\}\right),\left(\langle a\rangle ,\left\{a,b\right\}\right),\left(\langle b\rangle ,\left\{a,b\right\}\right)\right\}} 失敗/発散モデルは、失敗モデルを発散（divergence）を扱えるよう拡張したものである。失敗/発散モデルにおけるプロセスは ( f a i l u r e s ⊥ ( P ) , d i v e r g e n c e s ( P ) ) {\displaystyle \left(failures_{\perp }\left(P\right),divergences\left(P\right)\right)} という対で表され、 d i v e r g e n c e s ( P ) {\displaystyle divergences\left(P\right)} は発散を生じさせる全トレースの集合である。また、 f a i l u r e s ⊥ ( P ) = f a i l u r e s ( P ) ∪ { ( s , X ) | s ∈ d i v e r g e n c e s ( P ) } {\displaystyle failures_{\perp }\left(P\right)=failures\left(P\right)\cup \left\{\left(s,X\right)\vert s\in divergences\left(P\right)\right\}} が成り立つ。 初期の重要なCSPの応用例として、INMOS T9000 トランスピュータ の仕様記述と検証に使われた例がある。T9000 は複雑なスーパースケーラ型パイプラインプロセッサであり、大規模マルチプロセッシング可能なように設計されている。CSP はパイプラインの正当性検証と、Virtual Channel Processor というチップ間通信管理機能の検証に使われた。 ソフトウェア設計におけるCSPの応用は、主に人命に関わるような重要なシステムでなされている。例えば、Bremen Institute for Safe Systems と Daimler-Benz Aerospace は、国際宇宙ステーションで使用予定のシステム（約23,000行のコード）をCSPでモデル化し、デッドロックやライブロックが起きないことを検証した。このモデル化と解析によって、通常のソフトウェアテストでは検出が困難な問題をいくつか発見した。同様に Praxis High Integrity Systems でもセキュアなスマートカード認証システム（約100,000行のコード）の開発でCSPを使い、セキュリティとデッドロックが発生しないことの検証を行った。Praxis はシステムの欠陥率が他の同等のシステムよりも低くなったと主張している。 CSP はメッセージ交換を行う複雑なシステムのモデル化と解析に適しているため、通信プロトコルやセキュリティプロトコルの検証にも応用されてきた。特筆すべき応用例として、Needham-Schroeder 公開鍵認証プロトコルをCSPを使って検証し、未知の脆弱性を発見し、それに対処した新たなプロトコルを開発した例がある。 長年に渡って、CSPを使ってシステムを表現することで解析するツールがいくつも生み出されてきた。初期のツールはコンピュータが理解できるCSPの表現もまちまちだったため、ツール間で情報を共有できなかった。しかし最近では Bryan Scattergood の記法 CSPM が多くのCSPツールで使われている。CSPM には操作的意味論の形式定義があり、組み込みの関数型言語が含まれる。 もっとも有名なCSPツールとして Formal Systems Europe Ltd. が開発した商用製品である Failures/Divergence Refinement 2 (FDR2) がある。FDR2 はモデルチェッカとされることが多いが、技術的には改良チェッカである。すなわち、2つのCSPプロセス表現をラベル付き遷移系に変換し、指定された意味論モデル（トレース、失敗、失敗/発散）において、一方がもう一方の改良となっているかを調べる。 他にも以下のようなCSPツールがある。 ProBE ARC Casper CSP の影響を受けているその他の形式手法や仕様記述言語として、以下のものがある。 Timed CSP, リアルタイムシステム用にタイミング情報を追加したCSP Receptive Process Theory, 非同期（ブロックしない）送信操作を追加したCSP Wright, アーキテクチャ記述言語 TCOZ, Timed CSP と Object Z（オブジェクト指向を導入したZ言語）を統合したもの Circus, Z言語 と CSP を統合したもの CspCASL, CASL に CSP を統合したもの Limbo (プログラミング言語) - Inferno オペレーティングシステム内で並行性を実装した言語。CSPに影響を受けている。 Plan 9 from Bell Labs - C言語でCSP風の並行性を記述できるスレッドライブラリがある。