79

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

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

これは、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つの重要な問題、つまり、譲歩の欠如が1人の俳優が不当に過度の時間を消費する可能性があることを見逃していることを理解しています。しかし、クロスプラットフォームのコルーチンは、C ++でこれを困難にする主なものですか?(たとえば、Windowsにはファイバーがあります。)

しかし、私が見逃しているものは他にありますか、それともモデルは本当にこれほど明白ですか?

4

6 に答える 6

87

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

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

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

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

これらのことはC++で行うことは不可能ではありませんが、Erlangがほとんどすべての主要なhwおよびos構成で機能するという事実を追加すると、ますます困難になります。

編集:UlfWigerがerlangスタイルの並行性をどのように見ているかについての説明を見つけました。

于 2011-11-12T22:52:44.330 に答える
32

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

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

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

問題は、アクターを実装しないことです。それはそれほど難しいことではありません。問題は、プロセス、通信、ガベージコレクション、言語プリミティブ、エラー処理など、すべてを連携させることです。たとえば、OSスレッドの使用はスケールが悪いため、自分で行う必要があります。これは、1k個のオブジェクトしか持てず、作成と使用が重いオブジェクト指向言語を「販売」しようとするようなものです。私たちの観点からは、並行性はアプリケーションを構造化するための基本的な抽象化です。

夢中になっているのでここでやめます。

于 2011-11-13T21:59:25.137 に答える
22

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

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

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

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

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

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

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

于 2012-11-21T18:29:38.237 に答える
14

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

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

于 2011-11-13T07:51:09.363 に答える
3

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

これはすべて、確かに別の言語で「アクターモデル」プログラムスイートを作成できることを意味します(私は、Erlangに遭遇する前に、Python、C、Guileで長い間これを行ってきましたが、モニターとリンク、そして「アクターモデル」という用語を聞く前に)、コードが実際にどのように生成されるか、そしてそれらの間で何が起こっているかを理解する非常に難しいです。Erlangは、OSがカーネルの大規模なオーバーホールなしでは不可能であるというルールを適用します。カーネルのオーバーホールは、おそらく全体的には有益ではないでしょう。これらのルールは、プログラマーに対する一般的な制限(本当に必要な場合は常に回避できます)と、システムがプログラマーに保証する基本的な約束(本当に必要な場合は意図的に破ることができます)の両方として現れます。

たとえば、副作用からユーザーを保護するために、2つのプロセスが状態を共有できないように強制します。これは、すべてが参照透過性であるという意味ですべての関数が「純粋」でなければならないことを意味するのではなく(明らかに、プログラムの多くを参照透過性にすることは、ほとんどのErlangプロジェクトの明確な設計目標ですが)、2つです。プロセスは、共有状態または競合に関連する競合状態を常に作成しているわけではありません。(ちなみに、これはアーランの文脈での「副作用」の意味です。ハスケルやおもちゃの「純粋な」言語と比較した場合、アーランが「本当に機能するかどうか」を問う議論の一部を解読するのに役立つかもしれません。 。)

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

この約束に基づいて構築されているのは、モニターとリンクが正確であるという約束です。そして、それに基づいて、Erlangランタイムは、システムで何が起こっているのか(そしてerl_connectの使用方法...)を理解すると、「ネットワーククラスター」の概念全体を溶かしてしまいます。これにより、トリッキーな並行性のケースのセットをすでに乗り越えることができます。これにより、裸の並行プログラミングに必要な防御技術の沼地に夢中になるのではなく、成功したケースのコーディングに大きな一歩を踏み出すことができます。

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

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

于 2014-09-02T00:35:21.327 に答える
2

カサブランカは、アクターモデルブロックのもう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は、優れたインターフェイスの背後にパターンマッチングを隠すのに優れた仕事をしていることがわかりました。)

于 2012-05-01T05:49:33.833 に答える