web-dev-qa-db-ja.com

Scalaアクター:受信と反応

最初に、私は非常に多くのJavaの経験がありますが、最近は関数型言語に興味を持つようになりました。

しかし、私は Scalaでのプログラミング でScalaのActorフレームワークについて読んできましたが、理解できないことが1つあります。 30.4章では、reactの代わりにreceiveを使用するとスレッドの再利用が可能になり、スレッドはJVMで高価になるため、パフォーマンスに優れていると述べています。

これは、reactの代わりにreceiveを呼び出すことを覚えている限り、好きなだけアクターを起動できることを意味しますか? Scalaを発見する前に、私はErlangで遊んでいましたが、 Programming Erlang の著者は、汗をかきます。 Javaスレッド。Erlang(およびJava)と比較して、Scalaでどのような制限を検討していますか?

また、このスレッドはScalaでどのように再利用できますか?簡単にするために、スレッドは1つしかないと仮定します。私が開始するすべてのアクターはこのスレッドで順番に実行されますか、それとも何らかのタスク切り替えが行われますか?たとえば、メッセージを相互にピンポンする2つのアクターを開始した場合、それらが同じスレッドで開始された場合、デッドロックの危険がありますか?

ScalaのProgrammingによれば、reactを使用するアクターの作成は、receiveを使用するよりも困難です。 reactが返されないため、これはもっともらしい。ただし、本はActor.loopを使用して、reactをループ内に配置する方法を示しています。その結果、あなたは得る

loop {
    react {
        ...
    }
}

私には、これはかなり似ているようです

while (true) {
    receive {
        ...
    }
}

本の前半で使用されています。それでも、本は「実際には、プログラムは少なくとも数個のreceiveを必要とする」と述べています。だから私はここで何が欠けていますか? receiveは、戻る以外に、reactができないことを何ができますか?そして、なぜ私は気にしますか?

最後に、私が理解していないことの核心に迫ります。本は、reactを使用することでスレッドを再利用するために呼び出しスタックを破棄する方法を言及し続けています。それはどのように機能しますか?呼び出しスタックを破棄する必要があるのはなぜですか?また、関数が例外をスローして終了した場合(react)にコールスタックを破棄できるのはなぜですか?(receive)を返して終了した場合はできません。

Scalaでのプログラミングは、ここで重要な問題のいくつかを明らかにしてきた印象を持っています。それ以外の場合は本当に優れた本だからです。

110
jqno

まず、receiveで待機している各アクターはスレッドを占有しています。何も受信しない場合、そのスレッドは何も実行しません。 react上のアクターは、何かを受け取るまでスレッドを占有しません。何かを受け取ると、スレッドがそれに割り当てられ、初期化されます。

ここで、初期化部分が重要です。受信スレッドは何かを返すことが期待されていますが、反応スレッドはそうではありません。したがって、最後のreactの最後にある前のスタック状態は、完全に破棄される可能性があり、実際に破棄されます。スタック状態を保存または復元する必要がないため、スレッドの起動が速くなります。

いずれかが必要な場合、パフォーマンス上のさまざまな理由があります。ご存じのとおり、Javaにスレッドが多すぎることはお勧めできません。一方、スレッドをアクターに接続してからreactにすることができるため、 receiveよりもreactの方が速いため、多くのメッセージを受信するアクターが非常に少ない場合、reactの追加の遅延により、あなたの目的には遅すぎます。

78

答えは「はい」です。アクターがコード内の何かをブロックしておらず、reactを使用している場合、 "concurrent"を実行できます。単一スレッド内のプログラム(システムプロパティactors.maxPoolSizeをご覧ください)。

呼び出しスタックを破棄する必要がある理由の1つは、そうでない場合はloopメソッドがStackOverflowErrorで終わるためです。そのままで、フレームワークはreactをスローすることにより、SuspendActorExceptionをかなり巧妙に終了します。これは、reactメソッドを介してandThenを再度実行するループコードによってキャッチされます。

mkBodyActorメソッドを見てから、seqメソッドを見て、ループがどのように再スケジュールされるかを確認してください。

21
oxbow_lakes

「スタックを破棄する」というこれらのステートメントは、しばらくの間私を混乱させました。私は今それを手に入れたと思います。 「受信」の場合、メッセージに専用のスレッドブロッキングがあり(モニターのobject.wait()を使用)、これは完全なスレッドスタックが利用可能で、受信の「待機」のポイントから続行する準備ができていることを意味します。メッセージ。たとえば、次のコードがある場合

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

スレッドは、メッセージが受信されるまで受信呼び出しで待機し、「10の受信および印刷後」メッセージと、スレッドがブロックされる前のスタックフレームにある値「10」を続けて印刷します。

反応専用のスレッドがない場合、反応メソッドのメソッド本体全体がクロージャーとしてキャプチャされ、メッセージを受信する対応するアクター上の任意のスレッドによって実行されます。これは、クロージャのみとしてキャプチャできるステートメントのみが実行されることを意味し、「Nothing」の戻りタイプが再生されます。次のコードを検討してください

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

Reactの戻り値の型がvoidである場合、「react」呼び出しの後にステートメント(例では「反応して10を出力した後」のメッセージを出力するprintlnステートメント)を使用することは正当ですが、実際には"react"メソッドの本体のみがキャプチャされ、後で(メッセージの到着時に)実行するためにシーケンス化されるため、決して実行されません。 reactのコントラクトの戻り値の型は「Nothing」なので、reactに続くステートメントは存在できず、スタックを維持する理由はありません。上記の例では、反応呼び出し後のステートメントはまったく実行されないため、変数「a」を保持する必要はありません。反応の本体で必要なすべての変数は既にクロージャーとしてキャプチャされているので、問題なく実行できます。

Javaアクターフレームワーク Kilim は、メッセージを取得するリアクションで展開されるスタックを保存することにより、実際にスタックメンテナンスを行います。

20
Ashwin

ここにあるだけです:

制御の反転を伴わないイベントベースのプログラミング

これらの論文は、Actorのscala apiからリンクされており、actor実装の理論的フレームワークを提供します。これには、reactが返されない理由が含まれます。

8
Hexren

私はscala/akkaで大きな仕事をしていないが、アクターのスケジュール方法には非常に大きな違いがあることを理解している。Akkaは実行をスライスするスマートスレッドプールである俳優の...スライスごとに、命令ごとに実行できるErlangとは異なり、アクターによる完了までの1つのメッセージ実行になります。

これにより、現在のスレッドが他のアクターを考慮して、受信者が現在のスレッドをエンゲージして、同じアクターに対して他のメッセージの実行を継続するようにスケジューリングすることを示唆しているため、反応が優れていると考えるようになります。

0
user3407472