OCaml の Event モジュール
OCaml threads ライブラリを使ってみました。
Thread.create まではいいのですが外部からスレッドを終了させるのは難しかったです。
なにが難しかったかというと、Thread モジュールのなかに Thread.kill : Thread.t -> unit のようなメソッドがあるのですがこれを利用しようとしてもエラーを吐いて使い物になりません。
なので Event モジュールの channel を用いて通信が必要となります。
open Batteries let ch = Event.new_channel () let rec thread_0 () = print_endline "thread 0:"; match Event.poll (Event.receive ch) with None -> begin Thread.delay 0.5; thread_0 () end | Some _ -> begin print_endline "thread 0: exit"; Thread.exit () end let rec thread_1 () = print_endline "thread 1:"; Thread.delay 5.0; Event.poll (Event.send ch 1) |> ignore; (* ココに注目 *) print_endline "thread 1: exit"; Thread.exit () let _ = let t0 = Thread.create thread_0 () in let t1 = Thread.create thread_1 () in Thread.join t1; Thread.join t0
これは thread_0 と thread_1 という関数を新しくスレッドとして呼び出し、thread_1 が5秒経ってから thread_0 にチャンネルを用いて終了命令(ここでは 1:int)を送っています。送受信に Event.sync を使わず Event.poll 使っているのはブロッキングして欲しくないからです。
しかし、これを実行すると意図に反して thread_0 は死んでくれません。0.5秒ごとにメッセージを出し続けています。
myhost% ocamlfind ocamlopt -thread -package batteries -linkpkg main.ml -o main myhost% ./main thread 0: thread 1: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 1: exit thread 0: thread 0: thread 0: thread 0: ...
なぜでしょうか?
確定的なことは分かりませんが thread_0 が値を受信する前に thread_1 が終了してしまうとその送信イベント自体が無かったことになるように見えます。
どうやって解決するかというと単に kill メッセージを送信するときは poll じゃなくて sync を使います。
let rec thread_1 () = print_endline "thread 1:"; Thread.delay 5.0; Event.sync (Event.send ch 1) |> ignore; (* ココ *) print_endline "thread 1: exit"; Thread.exit ()
このように変更して実行すると二つのスレッドが正常に終了します。
myhost% ocamlfind ocamlopt -thread -package batteries -linkpkg main.ml -o main myhost% ./main thread 0: thread 1: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: thread 0: exit thread 1: exit
まとめ
ここまでは対処療法としてのノウハウになりますが、thread kill にしか使えません。channel を相変わらず理解できていない状態です。
ということで channel についてもう少し実験して調べてみたところ次のようなことがわかりました。poll はループ内からの呼び出しです。
送信 | sync | sync | poll | poll |
---|---|---|---|---|
受信 | sync | poll | sync | poll |
結果 | OK | OK | OK | NG |
送信側・受信側 両方とも poll はダメ。通信が成立しません。
channel というのは待ち行列ではないようです。poll は sync を用いて受信もしくは送信の待機状態にある相手が居るかどうかを確認し居れば送受信イベントを成立させるという動作をするようです。
注意点
また次のようなコードも確認して欲しいと思います。
open Batteries let ch = (Event.new_channel (),Event.new_channel ()) let rec thread_0 () = print_endline "thread 0:"; match Event.poll (Event.receive (fst ch)) with None -> begin Thread.delay 0.5; thread_0 () end | Some _ -> begin print_endline "thread 0: exit"; Event.poll (Event.send (snd ch) 1) |> ignore; Thread.delay 3.; (* これがないと上の send が完了しない *) Thread.exit () end let rec thread_1 () = print_endline "thread 1:"; Thread.delay 5.0; Event.sync (Event.send (fst ch) 1) |> ignore; Event.sync (Event.receive (snd ch)) |> ignore; print_endline "thread 1: exit"; Thread.exit () let _ = let t0 = Thread.create thread_0 () in let t1 = Thread.create thread_1 () in Thread.join t1; Thread.join t0
- thread_1 -> thread_0 : sync/poll
- thread_0 -> thread_1 : poll/sync
thread_0 が thread_1 からの終了命令に対し確認(ACK)を返します。
しかし、コード内でコメントを書いている行がないと thread_0 は終了しますが thread_1 は thread_0 からのメッセージを待ち続けて終了しない状態になります。
これはスレッドの終了が早過ぎて送信イベントが発生しないためだと考えられます。
したがって、スレッド終了直前でメッセージを送信する場合は sync でなくてはならないということになります。