ErlangとGolangを比較してみる
よくこの2つの言語を比較する人がいるように感じる。
両方ともサーバープログラム用途で用いられる言語なので、分からなくもないのだが、この2つの言語は似て非なる物だ。
プロセス周りに重点を置いて違いについて自分なりに纏めてみた。
両方とも触れてから1年と経っていないので、間違いがあれば指摘をもらえると嬉しい。
主な違い
Erlang | Golang | |
---|---|---|
言語の分類 | 関数型 | 手続き型 |
型 | 動的型付け (dialyzerによる型チェックあり) |
静的型付け |
変数 | immutable | mutable |
例外に関する思想 | Let it crash | Defensive Programming |
並行処理モデル | Message Passing (Actor Model) |
Message Passing (CSP/π caluculus) |
それぞれのプロセスの違い
Erlang | Golang | |
---|---|---|
ワーカースレッド数 | デフォルトでCPUのコア数 |
(基本的にはCPUのコア数で指定) Go 1.5からデフォルトでCPUのコア数になりました |
初期スタック | 309 words (1words≒64bit) |
8k bytes |
プロセス間メモリ共有 | なし | あり (global変数) |
プロセス数の上限 | デフォルト 32768 最大 268435456 |
数10万可能 |
プロセスの実行 | ワーカースレッドとバインドしない (固定することも可能) |
ワーカースレッドとバインドしない |
※ プロセス = Erlang Process / goroutine
並行処理モデルの違い
この部分を書くのに、arildさんのスライドを参考にさせてもらった。
図が非常に分かりやすいので、こちらも参照して欲しい。
Erlang: Actor model
- Process (Actor) が Mailboxを1つ持つ
- 通信はどのProcessに送るかが明確
- ブロック
- 受信側:Mailboxが空の場合ブロック
- 送信側:ブロックしない
Golang: CSP (Communication Sequential Processes)
- Channelを共有し、通信を行う
- ChannelとProcessは多vs多の関係 (どのプロセスが受け取るか分からない)
- ブロック
- 送信側と受信側が揃わないといけない
※ GolangはChannelサイズが指定できる為、サイズ>0の場合はChannelが空/満タンの場合にのみブロックする
スケジューラー
大量にProcessを立てた場合、どのように実行されるかに違いがある。
- Erlang
- 全てのProcessを均等に実行しようとする
- Golang
- 一定時間までは1つ (コア数分) のProcessを実行し続ける
(ver1.2まではProcessがsleep/終了するまで実行し続けていた)
プロセスの管理方法
Erlang
- プロセスツリーを構成する
Golang
- 管理しない
- 結果を待つことでデーモン化を防ぐ
例外処理
Erlang
- 基本的な例外の扱い方
- 例外をcatchせずプロセス自体をクラッシュさせる
- スーパーバイザ(管理プロセス)がプロセスの異常終了を検知
- 再起動戦略に従ってプロセスを立ち上げ直す
-module(ch_sup).
-behavior(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link(ch_sup, []).
init(_Args) ->
{ok, {{one_for_one, 1, 60},
[{ch3, {ch3, start_link, []},
permanent, brutal_kill, worker, [ch3]}]}}.
※ 公式の例より引用
再起動戦略
概要 | |
---|---|
one_for_one | 落ちたプロセスだけを再起動する |
one_for_all | 全ての子プロセスを再起動する |
rest_for_one | 子プロセスに依存関係がある場合に使用する |
simple_one_for_one | 子プロセスを動的に起動したい場合に使用する |
この辺りはlearn you some Erlangが詳しい
Golang
- 関数の返り値としてerrorかどうかを返す
- エラーの処理を書かせるようになっている
- 全ての変数はunusedでコンパイル時にエラーが出る
(ブランク変数/再代入を行うと処理忘れしてもエラーが出ないが……)
file, err := os.Open("hoge.txt")
if err != nil {
log.Fatal(err)
return
}
- 最悪の手段としてpanic (≒throw) / recover (≒catch) が提供されているが使用するべきではない
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic("panic")
補足1: panic/recoverの用途
recoverを標準ライブラリ内で使用している箇所について、ruiuさんがこちらの記事に書かれている
- 深いネストからトップレベル関数へ一気に戻る
- ランタイムエラーをcatchし、関数の返り値として返す
- プロセス全体が落ちないようにする為
要約すると上記3つの用途で使われているとのこと。
補足2: goroutine内でのpanic
-
panic
が発生すると全てのプロセスが終了する - goroutine内の
panic
は他のprocessでは処理できない
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
// 処理
}()
まとめ
Erlang/Golangをどんな時に選ぶとよいかについて個人的な考えをまとめる。
- Erlang
- Processが独立している場合 (Read/Writeが同じI/Fにできない場合など)
- 分散プログラムになる場合 (今回は触れていないが……)
- Processを大量に同時に立てる場合 (echo Server)
- Processを落としたくない場合
- Golang
- 複数のProcessが1つのQueueを共有したい場合
- 文字列処理/計算を扱う場合
- Processが落ちた場合でもそのProcessを再起動する必要がない場合
あくまでも個人的な意見だが、少しでも参考になれば幸いだ。