Memento memo.

Today I Learned.

The actor model in 10 minutes [日本語訳]

10分でわかるアクターモデルです。 Erlang, Elixir, Scalaあたりを触るときに理解すべき内容です。

元ネタ: www.brianstorti.com

Introduction

我々のCPUは速くなりません。 CPUには今やたくさんのコアを積んでいますが、 全てのハードウェア資源を利用したければ コードを並行的に走らせる必要があります。 マルチスレッドの利用が進むべき道でないことは、 数十年もの追跡不能バグや開発者の憂鬱が示しています。 しかし恐れることはありません。 素晴らしいいくつかの代替案があります。 今回はその中の一つを紹介しましょう。 それはActor Modelです。

Model

Actor Modelは並行計算を取り扱うための概念的なモデルです。 システムコンポーネントがどのように振る舞い、相互作用すべきかについてのいくつかの原則が定義されています。 Actor Modelを使っている最も有名な言語はErlangでしょう。 Actor Modelがどのように他の言語やライブラリで実装されているかについての話はしません。Actor Modelそれ自体に焦点を当てていきましょう。

Actors

Actorは、計算において最も基本的な(primitiveな)ユニットです。 Actorは、メッセージを受け取り、受け取ったメッセージに基いて何らかの計算を行う"もの"です。

イデア自体はオブジェクト指向言語のそれによく似ています。 オブジェクトはメッセージを受け取り(method call)、受け取ったメッセージ(call された method)に基いて何かを行います。

Actor ModelとOOPとの最も大きな違いは、 Actorは完全に独立しており、決してメモリを共有しないことです。 注目すべきは、Actorは決して他のActorからは直接書き換えることのできないprivateなステートを維持できる、ということです。

One ant is no ant

Actorは一つでは意味がありません。複数でシステムを形成します。 Actor Modelにおいては、全てはActorであり、Actorは他のActorがメッセージを送るためのアドレスを持つ必要があります。

Actors have mailboxes

複数のActorは同時に走る一方、一つのActorは受け取ったメッセージを連続的に(シーケンシャルに)処理する、というの理解することが重要です。 つまり、3つのメッセージを同じ一つのActorに送ったとして、 受け取った側のActorは単に一つずつシーケンシャルに処理します。 3つのメッセージを並行処理させたい場合は、3つのActorを作成し、それぞれに一つずつメッセージを送る必要があります。

複数メッセージは非同期的にActorに送られるので、他のメッセージを処理している間、それらをどこかに格納しておく必要があります。 メッセージの格納先こそが、メールボックスです。

http://www.brianstorti.com/assets/images/actors.png Actorたちは非同期メッセージを送り合いコミュニケーションを取ります。それらメッセージは、処理されるまでの間、他のActorのメールボックスにしまわれています。

What actors do

メッセージを受け取ったActorは、次の3つのうちのどれか一つを行うことができます。

  • 更なるActorを作成する
  • 他のActorにメッセージを行う
  • 次受け取るメッセージと何をすべきかを指定する

上2つは非常に直感的ですが、3つ目は興味深いですね。 先に述べた通り、Actorはprivateな状態を保持することができます。 "次受け取るメッセージと何をすべきかを指定する"というのは、 より明確にいうと、Actorが状態を変化させる方法です。

計算機のように振る舞うActorがあり、 最初のstateが 0 であると想定してください。 Actorが add(1) というメッセージを受け取った場合、 元のstateを変化させてしまう代わりに、 次受け取るメッセージに対して stateが 1 になるように指定します。

Fault tolerance

Erlangは"let it crash"という思想を取り入れました。 "全ての単一障害点について考えるのは不可能であるため、 起こりうる全ての可能性を予想し、それぞれ対処する方法を見つけるような 防御的プログラミングを行う必要はない"、という考えです。 Erlangの手法は、すぐにcrashさせる代わりに、この致命的なコードを誰か(スーパーバイザ)に監視させることです。スーパーバイザの単一責務は、crashが起きた場合に何をすべきか(例として、crashしたユニットを正常な状態にresetする)を把握することです。 これを可能にしているのがActor Modelです。

全てのコードはプロセス(ErlangがActorと呼んでいるもの)の中で走ります。 プロセスは完全に独立・隔離されているので、プロセスのstateが他のプロセスの影響を受けることはありません。 スーパーバイザ(スーパーバイザもプロセス = Actor)は、 監視対象のプロセスがクラッシュすると通知され、何かの処理を行うことができます。

これらの仕組みにより自己修復(self heal)機能を持ったシステムを作ることが可能です。 つまり、あるActorが何らかの理由で例外的なstateになりcrashした場合、 スーパーバイザはcrashしたActorを再び整合性の取れたstateにすることができます。具体的な戦略はいくつかありますが、一般的なのはActorを初期状態にしてrestartすることです。

Distribution

メッセージを送るActorがローカルに存在するのか、離れたノードに存在するのかについては全く重要ではない、というのが Actor Modelの他の興味深い側面です。

考えてみてください。Actorがメールボックスと内部状態を持ち、メッセージに応答するだけのコード片だとしたら、実際にActorが動いているマシンが何であるかなんて誰が気にかけるでしょうか。メッセージがそこに届きさえすればそれで問題ありません。

おかげで、我々は複数のマシンを使ったシステムを構築でき、一つのノードに障害が起きたとしても、復旧が容易になります。

プログラミングElixir

プログラミングElixir