LoginSignup
3
3

More than 5 years have passed since last update.

Erlang プロセスの終わらせ方と error/1, throw/1, exit/1

Last updated at Posted at 2016-10-25

この資料は

error/1, throw/1, exit/1 の使い分けについて.
gg っても日本語のいいまとめが出てこないので,じゃあ書こうじゃないかと.
正確にまとめられてるかわからないですけど.

とりあえず catch してみましょうぞ

error/1, throw/1, exit/1 をそれぞれ catch した.

1> catch error(hoge).
{'EXIT',{hoge,[{erl_eval,do_apply,6,
                     [{file,"erl_eval.erl"},{line,673}]},
           {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,431}]},
           {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
           {shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
           {shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}}
2> catch throw(hoge).
hoge
3> catch exit(hoge).
{'EXIT',hoge}

これから推測されることは以下.

  • error/1exit/1 はプロセスを殺す力を持っている(DOWN)が, throw/1 は持っていない.
  • throw/1 は大域脱出でしかない.プロセスを殺す力を持っておらず,投げられた例外は同一プロセス内でなんとかしなければならない.
  • exit/1 ではスタックトレースはつかないのは, exit/1 が正常系のプロセス死にも利用されるから.

使い分け

catch した結果をふまえると, この 3 つの関数の使い分けは以下の感じでいいだろう.

exit/1

文字通りプロセスを終了させたい場合に用いる.
単に役目が終わったプロセスを明示的に殺すだけなので,エラーではないのだね.

Eshell V7.0  (abort with ^G)
1> spawn(fun() -> exit(hogehoge) end).
<0.35.0>
2> 

monitor すればプロセスが死んだ Reason がとれる.

Eshell V7.0  (abort with ^G)
1> spawn_monitor(fun() -> exit(hogehoge) end).
{<0.35.0>,#Ref<0.0.4.29>}
2> flush().
Shell got {'DOWN',#Ref<0.0.4.29>,process,<0.35.0>,hogehoge}
ok

throw/1

なにかエラーが発生したわけではない.ネストした関数呼び出しから一気に脱出 (大域脱出)するために用いる.
プロセス内でハンドリングされることを想定しているため,その try ... catch がないとエラーとなりプロセスが死ぬ.

Eshell V7.0  (abort with ^G)
1> spawn(fun() -> throw(hogehoge) end).
<0.35.0>

=ERROR REPORT==== 24-Oct-2016::18:58:27 ===
Error in process <0.35.0> with exit value:
{{nocatch,hogehoge},[{erlang,apply,2,[]}]}

monitor してみると,ブロセスが死んだ直接的な原因は, hogehoge が throw されたからではなくて, throw された hogehoge が catch されなかったから,ということになっている.

1> spawn_monitor(fun() -> throw(hogehoge) end).

=ERROR REPORT==== 24-Oct-2016::18:59:41 ===
Error in process <0.37.0> with exit value:
{{nocatch,hogehoge},[{erlang,apply,2,[]}]}
{<0.37.0>,#Ref<0.0.4.34>}
2> flush().
Shell got {'DOWN',#Ref<0.0.4.34>,process,<0.37.0>,
              {{nocatch,hogehoge},[{erlang,apply,2,[]}]}}

つまりプロセスを殺すことが目的だったら, throw/1 は不適切.

error/1

何か問題が発生した. badmatch とか case_clause は Erlang VM が吐いてくれるエラーで,それらと同じレベルで,なにかユーザ定義の「問題」が発生したよ,というのが error.
プロセスは続行できなくなり死ぬ.

4> spawn(fun() -> error(hogehoge) end).

=ERROR REPORT==== 24-Oct-2016::19:04:01 ===
Error in process <0.40.0> with exit value:
{hogehoge,[{erlang,apply,2,[]}]}
<0.40.0>

monitor してみると,見た目は throw と一緒. throw でプロセスが死ぬのは, nocatch エラーだから.
ただし, error/1 の引数で指定した Reason (hogehoge) がそのままエラーの原因になっている (nocatch はつかない).

5> spawn_monitor(fun() -> error(hogehoge) end).
{<0.42.0>,#Ref<0.0.4.51>}

=ERROR REPORT==== 24-Oct-2016::19:04:14 ===
Error in process <0.42.0> with exit value:
{hogehoge,[{erlang,apply,2,[]}]}
6> flush().
Shell got {'DOWN',#Ref<0.0.4.51>,process,<0.42.0>,
              {hogehoge,[{erlang,apply,2,[]}]}}

まとめ

プロセスが死ぬ原因は 2 つ.

  1. exit/1 により明示的に殺した(死んだ)
  2. エラーが発生し,実行が続行できなくなった

それぞれをユーザが明示的にコーディングする方法(として適切なの)は,それぞれ以下.

  1. exit/1 で明示的に殺す.
  2. error/1 で明示的にエラーを発生させる.

つまり, error/1, throw/1, exit/1 の使い分けは,簡単に書けば以下.

  • error/1: プロセスの実行が続行できなくなるようなエラーが発生した
  • exit/1: プロセスは役割を果たしたので死ぬ (もしくは死ぬことで役割を果たす)
  • throw/1: 大域脱出
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3