web-dev-qa-db-ja.com

アクターモデル:なぜアーランは特別なのですか?または、なぜ別の言語が必要なのですか?

私はアーランの学習を検討しており、その結果、俳優のモデルについて読んでいます(大丈夫、スキミング)。

私が理解していることから、アクターモデルは単なる関数のセット(erlangでは「プロセス」と呼ばれる軽量スレッド内で実行される)であり、メッセージパッシングを介してのみ互いに​​通信します。

これは、C++またはその他の言語で実装するのはかなり簡単なようです。

class BaseActor {
    std::queue<BaseMessage*> messages;
    CriticalSection messagecs;
    BaseMessage* Pop();
public:
    void Push(BaseMessage* message)
    {
        auto scopedlock = messagecs.AquireScopedLock();
        messagecs.Push(message);
    }
    virtual void ActorFn() = 0;
    virtual ~BaseActor() {} = 0;
}

各プロセスは、派生したBaseActorのインスタンスです。アクターは、メッセージパッシングを介してのみ相互に通信します。 (つまり、押す)。アクターは、初期化時に中央のマップに自分自身を登録します。これにより、他のアクターがそれらを見つけ、中央の機能がそれらを実行できるようになります。

さて、私はここで1つの重要な問題を見落としている、またはむしろ見落としていることを理解しています。しかし、クロスプラットフォームのコルーチンは、C++でこれを困難にする主な原因ですか? (たとえば、Windowsにはファイバーがあります。)

他に何か足りないものはありますか、それともこのモデルは本当に明白ですか?

私は間違いなくここで炎上戦争を始めようとはしていません。欠けているものを理解したいだけです。これは本質的に、並行コードについていくらか推論できるようにするためにすでに行っていることだからです。

76
Jonathan Winks

C++コードは、Erlangがそのアクターモデルの一部としてもたらすすべてのものである公平性、分離、障害検出、または分散を処理しません。

  • 他の俳優を飢餓状態にすることは許可されていません(公正)
  • 1つのアクターがクラッシュした場合、そのアクター(分離)のみに影響します。
  • 1つのアクターがクラッシュした場合、他のアクターはそのクラッシュを検出して対応できるはずです(障害検出)。
  • アクターは、同じマシン上にあるかのようにネットワークを介して通信できる必要があります(配布)

また、ビームSMPエミュレーターは、アクターのJITスケジューリングをもたらし、アクターを現時点で最も使用率の低いコアに移動し、不要になった場合は特定のコアのスレッドを休止状態にします。

さらに、Erlangで記述されたすべてのライブラリーとツールは、これが世界が機能する方法であり、それに応じて設計されると想定できます。

これらのことはC++では不可能ではありませんが、Erlangがほとんどすべての主要なハードウェアおよびOS構成で機能するという事実を追加すると、ますます難しくなります。

編集: lf Wiger によるアーランスタイルの同時実行性に関する説明を見つけたところです。

83
Lukas

私は自分自身を引用するのは好きではありませんが、 Virdingのプログラミングの最初のルール から

別の言語の十分に複雑な並行プログラムには、Erlangの半分の非公式に指定されたバグに覆われた遅い実装が含まれています。

グリーンスパンに関して。ジョー(アームストロング)にも同様のルールがあります。

問題は、アクターを実装することではありません。それはそれほど難しくありません。問題は、すべてを連携させることです。たとえば、プロセス、通信、ガベージコレクション、言語プリミティブ、エラー処理など。これは、OO 1kのオブジェクトしか持てず、作成と使用が重い言語を "販売"しようとするようなものです。 。

夢中になるのでここで止めます。

29
rvirding

これは実際には優れた質問であり、おそらくまだ説得力のない優れた答えを受け取っています。

すでにここにある他の素晴らしい答えに陰影と強調を加えるには、フォールトトレランスと稼働時間を達成するために、Erlang (takes)(C/C++などの従来の汎用言語と比較)を検討してください。

まず、ロックを解除します。 Joe Armstrongの本は、この思考実験を説明しています。たとえば、プロセスがロックを取得してすぐにクラッシュしたとします(メモリグリッチが原因でプロセスがクラッシュするか、電源がシステムの一部に失敗します)。プロセスが次に同じロックを待機するとき、システムはちょうどデッドロックしました。これは、サンプルコードのAquireScopedLock()呼び出しのように、明らかなロックである可能性があります。または、malloc()またはfree()を呼び出すときなど、メモリマネージャーがユーザーに代わって取得した暗黙的なロックである可能性があります。

いずれにしても、プロセスのクラッシュにより、システム全体の進行が停止しました。フィニ。話の終わり。あなたのシステムは死んでいます。 C/C++で使用するすべてのライブラリがmallocを呼び出さず、ロックを取得しないことを保証できない限り、システムはフォールトトレラントではありません。 Erlangシステムは、処理を進めるために高負荷のときにプロセスを強制終了できます。そのため、大規模な場合、スループットを維持するために、Erlangプロセスは(任意の単一ポイントで)強制終了できなければなりません。

部分的な回避策があります。ロックの代わりにどこでもリースを使用しますが、使用するすべてのライブラリがこれも行う保証はありません。そして、正しさについての論理と推論はすぐに非常に難しくなります。さらに、リースはゆっくりと回復します(タイムアウトの期限が切れた後)。そのため、障害が発生してもシステム全体が本当に遅くなります。

次に、Erlangは静的型付けを取り除きます。これにより、ホットコードのスワップと、同じコードの2つのバージョンの同時実行が可能になります。つまり、システムを停止することなく、実行時にコードをアップグレードできます。これは、システムが9の9秒間または32ミリ秒のダウンタイム/年を維持する方法です。彼らは単にその場でアップグレードされます。アップグレードするには、C++関数を手動で再リンクする必要があります。同時に2つのバージョンを実行することはサポートされていません。コードのアップグレードにはシステムのダウンタイムが必要です。一度に複数のバージョンのコードを実行できない大規模なクラスターがある場合は、クラスター全体を一度に停止する必要があります。痛い。そして、テレコムの世界では、許容できません。

さらに、Erlangは共有メモリと共有共有ガベージコレクションを取り除きます。各軽量プロセスは、個別にガベージコレクションされます。これは最初のポイントを単純に拡張したものですが、真のフォールトトレランスを実現するには、依存関係の観点から連動していないプロセスが必要であることを強調しています。これは、大規模なシステムの場合、GCがJavaに比べて一時停止が許容される(8 GBのGCが完了するまでに30分を一時停止するのではなく))ことを意味します。

21
jaten

C++には実際のアクターライブラリがあります。

そして いくつかのライブラリのリスト 他の言語の場合。

14
Alexey Romanov

それは俳優モデルについてははるかに少なく、C++でOTPに類似したものを適切に書くことがどれほど難しいかについての多くです。また、オペレーティングシステムが異なれば、根本的に異なるデバッグとシステムツールが提供され、ErlangのVMといくつかの言語構成要素は、これらのプロセスすべてが何をしているのかを把握する統一された方法をサポートします。いくつかのプラットフォームで統一された方法で(またはおそらくまったく)実行します(Erlang/OTPは現在の話題が「俳優モデル」という用語よりも古いことを覚えておくことが重要です。そのため、これらの種類の議論はリンゴとテロダクティル;素晴らしいアイデアは独立した発明になりがちです。)

これはすべて、「アクターモデル」のプログラムスイートを別の言語で確実に作成できることを意味します(私は知っていますが、これはPython、C、Guileで、Erlangに出会う前に、これを実現する前に、モニターとリンク、そして「アクターモデル」という言葉を耳にする前に、コードが実際に生成されるプロセスと何が起こっているのかを理解するそれらの中で=は非常に困難です。 Erlangは、OSが主要なカーネルオーバーホールなしには絶対にできないルールを適用します。カーネルオーバーホールはおそらく全体的には有益ではありません。これらのルールは、プログラマーに対する一般的な制限(本当に必要な場合は常に回避することができます)と、プログラマーにシステムが保証する基本的な約束(本当に必要な場合は意図的に破ることができる)の両方として現れます。

たとえば、2つのプロセスで状態を共有して副作用から保護することはできません。これは、すべての関数が「純粋」である必要があるという意味ではありません。すべての関数が参照的に透過的であるという意味ではありません(もちろん、プログラムのできるだけを参照的に透過的にすることは実用的であるため、ほとんどのErlangの明確な設計目標です)ただし、2つのプロセスが共有状態または競合に関連する競合状態を常に作成しているわけではありません。 (ところで、これはErlangのコンテキストでの「副作用」の意味です。Haskellやおもちゃの「純粋な」言語と比較すると、Erlangが「本当に機能するかどうか」を問う議論の一部を解読するのに役立つ場合があります。 。)

一方、Erlangランタイムはメッセージの配信を保証します。これは、管理されていないポート、パイプ、共有メモリ、および一般的なファイルを介して純粋に通信する必要がある環境では見逃されているものであり、OSカーネルが唯一の管理対象であり、これらのリソースのOSカーネル管理は、Erlangと比較すると、非常に最小限です。ランタイムが提供します)。これは、ErlangがRPCを保証することを意味しているわけではありません(とにかく、メッセージの受け渡しはnot RPCでも、メソッドの呼び出しでもありません)。メッセージが正しくアドレス指定されていることを保証するものではありません。メッセージを送信しようとしているプロセスが存在するか、または生きていることを約束します。送信先がその時点で有効な場合は、配信が保証されるだけです。

この約束の上に築かれているのは、モニターとリンクが正確であるという約束です。そして、それに基づいて、Erlangランタイムは、システムで何が行われているのか(およびerl_connect ...の使用方法)を把握すると、「ネットワーククラスター」の概念全体を溶け込ませます。これにより、一連のトリッキーな同時実行性のケースを乗り越えることができます。これにより、ネイキッドコンカレントプログラミングに必要な防御技術の沼に陥るのではなく、成功したケースのコーディングに大きなスタートを切ることができます。

したがって、実際には必要 Erlangについてではなく、言語、ランタイムおよびOTPについては既に存在し、かなりクリーンな方法で表現され、それに近いものを別の言語で実装することは非常に困難です。 OTPは従うのが難しい行為です。同じように、実際にはneed C++でもありません。生のバイナリ入力であるBrainfuckに固執して、アセンブラーを高水準言語と見なすことができます。歩く方法や泳ぐ方法を知っているので、電車や船も必要ありません。

そうは言っても、VMのバイトコードは十分に文書化されており、それにコンパイルしたり、Erlangランタイムで動作する多くの代替言語が登場しています。質問を言語/構文の部分(「同時実行を行うにはMoon Runesを理解する必要がありますか?」)とプラットフォームの部分(「OTPは同時実行を行う最も成熟した方法ですか? 、最も一般的な落とし穴は、並行分散環境で見つかりますか?」)の場合、答えは(「いいえ」、「はい」)です。

3
zxq9

Casablanca は、アクターモデルブロックのもう1つの新しい子供です。典型的な非同期受け入れは次のようになります。

PID replyTo;
NameQuery request;
accept_request().then([=](std::Tuple<NameQuery,PID> request)
{
   if (std::get<0>(request) == FirstName)
       std::get<1>(request).send("Niklas");
   else
       std::get<1>(request).send("Gustafsson");
}

(個人的に、私は [〜#〜] caf [〜#〜] がニースインターフェースの背後にあるパターンマッチングを隠すのに優れていることを発見しました。)

2
mavam