typedef phil = int哲学者を表わす型として [int] の代わりに [phil] を使う唯一の目的は、後続のコードをより簡単に表現するためです。
この問題で描写されているフォークは共有リソースです。 次にフォークを表わす線形抽象型を導入しましょう:
absvt@ype fork = int
この宣言は [fork] はサイズが [int] のサイズに等しいような抽象 viewtype
(つまり線形抽象型) であることを意味しています。
[fork] と [int] が同じサイズであるという情報は、型検査が終わって抽象型が具象型で置き換えられるまでは有効ではないことに注意してください。
哲学者が彼の左のフォークを獲得/解放するために、次の関数を呼び出すことができます:
fun phil_acquire_lfork (n: phil): fork fun phil_release_lfork (n: phil, f: fork): void哲学者が彼の右のフォークを獲得/解放するために、次の関数を呼び出すことができます:
fun phil_acquire_rfork (n: phil): fork fun phil_release_rfork (n: phil, f: fork): voidフォークは共有リソースなので、フォークの獲得/解放を表わすこれらの関数は一般になんらかのロックメカニズムを必要とします。 フォークが獲得されたら、それを使った後には解放されるべきです。 フォークに線形型を割り当てることで、フォークの経過を追いかけてそれらが適切に扱われていることを保証するために、ATS の型システムを信頼することができます。
extern fun phil_loop (n: phil): void implement phil_loop (n) = let val () = phil_think (n) val ((*void*)) = phil_dine (n) in phil_loop (n) end // end of [phil_loop]リソースが不要な関数 [phil_think] は次のように実装できます:
extern fun phil_think (n: phil): void implement phil_think (n) = ( randsleep (10) // for sleeping up to 10 secs )リソースの獲得と解放を必要とする関数 [phil_dine] は次のように実装できます:
extern fun phil_dine (n: phil): void implement phil_dine (n) = let // val lf = phil_acquire_lfork (n) val () = randsleep (1) // for sleeping up to 1 secs val rf = phil_acquire_rfork (n) // val () = randsleep (3) // for sleeping up to 3 secs // val () = phil_release_lfork (n, lf) val () = phil_release_rfork (n, rf) // in // nothing end // end of [phil_dine][lf] と [rf] 両方には線形型 [fork] が割り当てられているので、それらは線形値であることに注意してください。 あらゆる線形値はなんらかの方法で消費もしくは返されなればならないので、呼び出し [phil_release_lfork] (もしくは [phil_release_rfork]) を削除すると型検査でたやすく検出可能な型エラーを引き起こします。 これはリソースを表現する値に線形型を割り当てる大きな利点です。
5人の食事する哲学者の問題は、デッドロックとデッドロックの回避に対する学習の導入になりました。 与えられた実装を実行すると、数分の内にデッドロックが発生するのを目にするでしょう。 デッドロックしたプロセスを全て削除するために、どうか [kill] コマンドを使ってください。