OCamlではCMLスタイルのスレッド間通信が可能です。 Eventモジュールには基本的なイベント型やその同期関数が揃っています。 ところが、ivarやmvarといった共有変数が無い為に苦しい状況になっています。

例えばとても単純に、あるスレッドに整数を渡して、計算結果を(今回は倍にして)返してくれるスレッドを 作ってみましょう。すぐに思いつくのは、次のようなコードです。

let start_server () =
  let in_ch, out_ch =
    new_channel (), new_channel ()
  in
  let rec loop () =
    let x = sync (receive in_ch) in
    loop (sync (send out_ch (x * 2)))
  in
  ignore (Thread.create loop ());
  in_ch, out_ch
let calc (in_ch, out_ch) x =
  wrap (send in_ch x) (fun () -> sync (receive out_ch))

これで全く問題ないです。ただし、同期ポイントが要求を送るときになっている事に注目してください。 要求を送る時と応えを受け取る時の二種類の同期ポイント候補がありますので、仮に応えを受け取るときを 同期ポイントにしたい場合には、次のようになります。

let calc (in_ch, out_ch) x =
  guard (fun () ->
    sync (send in_ch x);
    receive out_ch)

さぁ、ここから問題が発生します。receive out_chが同期ポイントなので、応えを受け取るかどうかを他 のイベントと選択(select)できるようになります。すると、「ガードが発動して要求を送っているのに、 receiveが選ばれない」という状況が発生します。結果、サーバースレッドの sync (send ...が待ちに 入り、サーバーがロックダウンします。 (poll (send ...でいいじゃないかと思われるかもしれませんが、sync (send in_ch x)した後、たま たまreceive out_chする前にpollが発動する可能性がなきにしもあらずなので、不確実になります。マル チスレッドプログラミングでは、この手の「微妙」なタイミング問題を排除しないと安心できません。)

この問題への最後の手段としてwrap_abortがあります。

let start_server () =
  let in_ch, out_ch =
    new_channel (), new_channel ()
  in
  let rec loop () =
    let nack, x = sync (receive in_ch) in
    loop (select [send out_ch (x * 2)); receive nack])
  in
  ignore (Thread.create loop ());
  in_ch, out_ch
let calc (in_ch, out_ch) x =
  guard (fun () ->
    let nack = new_channel () in
    sync (send in_ch (nack, x));
    wrap_abort (receive out_ch) (fun () -> Thread.create (fun () -> sync (send nack ())) ()))

やった、これで解決です、お疲れさまでした。でも待ってください。毎回nackまで考えなきゃいけないんですか? それに仲介役のスレッドを起動しています。リソース的に優しくありません。

そこで、CMLではivarというスレッド間共有変数を抽象化した便利グッズが用意されています。

from http://cml.cs.uchicago.edu/pages/sync-var.html

type 'a ivar

This is the type constructor for I-structured variables. I-structured variables are write-once variables that provide synchronization on read operations. They are especially useful for one-shot communications, such as reply messages in client/server protocols, and can also be used to implement shared incremental data structures.

まさに、ivarは応えを送るのに最適と書いてあります。実はCMLのivarは内部的にスレッドを使わずに実装されており、 ivarを使う事で劇的にパフォーマンスが上がると「Concurrent Programming in ML」にも書いてありました。 ivarを使うと、次のように書けます。

let start_server () =
  let in_ch, out_ch =
    new_channel (), new_channel ()
  in
  let rec loop () =
    let ivar, x = sync (receive in_ch) in
    loop (sync (Ivar.put ivar (x * 2))
  in
  ignore (Thread.create loop ());
  in_ch, out_ch
let calc (in_ch, out_ch) x =
  guard (fun () ->
    let ivar = Ivar.make () in
    sync (send in_ch (ivar, x));
    Ivar.read ivar)

ivarすばらしい。nackもwrap_abortも要らないし、簡単じゃありませんか。しかし、冒頭にも紹介しましたが、実はOCamlにはないのです、Ivarが。おーぃ(泣。

 
 

でも大丈夫。実装しました→concurrent cell。OCamlでも安心してマルチスレッドプログラミングをお楽しみ下さい。 ちなみにconcurrent cellにはキューやブロードキャストやRPCなど、さらに高度なプロトコルも同梱されています。

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS