Edited at
ErlangDay 6

ErlangとGolangを比較してみる

More than 3 years have passed since last update.


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のコア数

デフォルトで1
(基本的には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


  • 基本的な例外の扱い方


    1. 例外をcatchせずプロセス自体をクラッシュさせる

    2. スーパーバイザ(管理プロセス)がプロセスの異常終了を検知

    3. 再起動戦略に従ってプロセスを立ち上げ直す



-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を再起動する必要がない場合



あくまでも個人的な意見だが、少しでも参考になれば幸いだ。