Erlang/Elixir のプロセスを5歳児でも分かるくらい丁寧に説明する記事(draft)
Erlang を始めて最初によく分からないと感じたのはあの癖の強い構文でしたが、一番良くわからなかったのは Erlang の目玉機能と言っても良い並列指向プログラミングをかじるようになった辺りでした。
突然現れた Erlang プロセス、メッセージパッシング、エラー処理、etc。この記事では、自分より後に Erlang を学び始めた人が少しでもこれら並列実行を構成する要素に惑わされないようにいくつかの要素を丁寧に説明していきたいと思います。
実行環境
まず大前提ですが、 Erlang は コンパイル言語 です。
コンパイルされたソースコードは中間言語(.beam
)に変換され、Erlang の実行環境 BEAM (Erlang Virtual Machine)で動作します。BEAM 自体は C 言語で作成されていて、 GitHub で公開されている ので誰でもソースコードを覗くことができます。(蛇足ですが、BEAM は Java で言うところの JVM、Ruby で言うところの YARV です)
さて、Erlang が注目されている点として、Erlang は並列指向プログラミングが可能な言語であるという点が挙げられます。この部分を理解しようと思うなら、BEAM がどうやって生成された中間言語を実行しているかという点を探ることが近道でしょう。
BEAM の仕事は単純にコンパイルされた中間言語を読み解いて実行することですが、Erlang の並列性を実現するため実行にはいくつかの要素を取り入れています。例えば、プロセス、メールボックス、スケジューリングなど。そして、この要素のいくつかを丁寧に説明することがこの記事の本題です。
Erlang プロセス
Erlang の並列性を実現するための要素としてまず現れるのは Erlang プロセス です。
BEAM は並列性を実現するため、Erlang を実行する仕組みに OS のプロセスと同じような仕組みを取り入れています。概念的には、BEAM という OS の上で動作する Erlang プロセスという認識で間違いはありません。Erlang プロセスは OS のプロセスのようにメモリやストレージまで完全に分離されています。(Golang の goroutine に近いものがあると感じています)
ただこの Erlang プロセス、OS のプロセスほど重くも複雑でもありません。実際の所、BEAM の中での Erlang プロセスの実体は process
という名前の C の小さな構造体です。大変軽量なため、Erlang は Ruby で1つのオブジェクトを作るくらいの感覚でプロセスを生成することができます。そして多数生成したプロセスがプロセス間通信を行いながらプログラムが実行される部分が Erlang の醍醐味でもあります。
Erlang プロセスには次のようなデータが格納されています。
- プロセス ID
- ステート(状態保存用)
- メールボックス(Queue)
- 関数(メッセージリスナー)
- etc
完全に分離されているので外部からステートの情報を操作することは出来ません。また、プロセス ID は分離されたプロセス同士がプロセス間通信を行うための住所のようなものです。
例えば、Erlang には erl
で起動できる対話形式で実行できる Erlang エミュレーターがありますが、このエミュレーター自体も実体は BEAM で動作するプロセスの1つです。実行中プロセスの ID はビルドイン関数 erlang:self/0
で確認することができます。
1> self(). <0.79.0>
(この <0.79.0>
みたいなのがプロセス ID です)
他の要素であるメールボックスやメッセージリスナーなどの機能はプロセス間通信を行うための仕組みであり、各プロセスに実装されているものです。
さて、ここまででもう Erlang が BEAM のプロセスを駆使して実行されていることと、Erlang プロセスがどういうものなのかを少しは分かってもらえたと思います。次からはプロセス同士が通信するための仕組みを見ていきましょう。
補足:ステート
プロセスには状態管理のために確保された専用ストレージ(ステート)が存在しています。 そのプロセスによってのみ変更可能で、基本的には関数とやり取りを行って状態を変化させていきます。
プロセスを生成するために BEAM はヒープ領域を確保しますが、初期のヒープ領域が小さめに設定されている理由は何十万ものプロセスを同時に実行するためだとかなんとか。
補足:メールボックス
他プロセスから自プロセスに送信されたメッセージを保存するためのキューを メールボックス と呼びます。プロセス間通信の基本は、送信側が相手のメールボックスにメッセージを push し、受信側が自分のメールボックスからメッセージを pop することにあります。