web-dev-qa-db-ja.com

猫の効果と非同期IO詳細

数日間、猫の効果とIOに頭を抱えてきました。そして、私はこの効果についていくつかの誤解を持っているか、単にそのポイントを逃したと感じています。

  1. まず、IOでScalaのFutureを置き換えることができる場合、非同期を作成するにはどうすればよいですかIOタスク?IO.shiftを使用?IO.asyncを使用?IO.delay同期ですか、非同期ですか?このAsync[F].delay(...)のようなコードで一般的な非同期タスクを作成できますか?または、IOをunsafeToAsyncまたはunsafeToFutureで呼び出すと非同期になりますか?
  2. 猫効果における非同期と同時のポイントは何ですか?なぜ分離されているのですか?
  3. IO緑のスレッドですか?はいの場合、猫効果にファイバーオブジェクトがあるのはなぜですか?私が理解しているように、ファイバーは緑のスレッドですが、ドキュメントではIOを緑のスレッドと見なすことができると主張しています。 。

私はそれらの猫効果のドキュメントを理解するのに失敗し、インターネットはそれほど役に立たなかったので、これらのいくつかについて明確にしていただければ幸いです...

15
ukulele

IOがScalaのFutureを置き換えることができる場合、非同期を作成するにはどうすればよいですかIOタスク

まず、非同期タスクの意味を明確にする必要があります。通常asyncは「OSスレッドをブロックしない」ことを意味しますが、Futureについて言及しているため、少しぼやけています。たとえば、私が書いた場合:

_Future { (1 to 1000000).foreach(println) }
_

asyncにはなりません。これはブロッキングループとブロッキング出力であるためですが、暗黙のExecutionContextによって管理されているため、別のOSスレッドで実行される可能性があります。同等の猫効果コードは次のようになります。

_for {
  _ <- IO.shift
  _ <- IO.delay { (1 to 1000000).foreach(println) }
} yield ()
_

(それは短いバージョンではありません)

そう、

  • _IO.shift_は、スレッド/スレッドプールを変更するために使用されます。 Futureはすべての操作でそれを行いますが、パフォーマンスの面で自由ではありません。
  • _IO.delay_ {...}(別名_IO { ... }_)は[〜#〜] not [〜#〜]何でも非同期にして[〜#〜 ] not [〜#〜]スレッドを切り替えます。同期副作用APIから単純なIO値を作成するために使用されます

では、true asyncに戻りましょう。ここで理解することはこれです:

すべての非同期計算は、コールバックを受け取る関数として表すことができます。

Futureを返すAPIを使用していても、JavaのCompletableFutureを使用していても、NIO CompletionHandlerのようなものを使用していても、すべてコールバックに変換できます。これが_IO.async_の目的です。コールバックを取得する関数を取るコールバックIOに変換できます。そして、次のような場合:

_for {
  _ <- IO.async { ... }
  _ <- IO(println("Done"))
} yield ()
_

Doneは、_..._の計算がコールバックした場合(およびその場合)にのみ出力されます。緑のスレッドをブロックしているが、OSスレッドはブロックしていないと考えることができます。

そう、

  • _IO.async_はすでに非同期計算をIOに変換するためのものです。
  • _IO.delay_は完全同期計算をIOに変換するためのものです。
  • 真に非同期の計算を行うコードは、グリーンスレッドをブロックしているように動作します。

Futuresを使用する場合の最も近い例は、_scala.concurrent.Promise_を作成して_p.future_を返すことです。


または、IOをunsafeToAsyncまたはunsafeToFutureで呼び出すと、非同期が発生しますか?

ちょっと。 IOでは、これらのいずれかを呼び出す(またはIOAppを使用する)場合を除き、-nothingが発生します。ただし、IOは、_IO.shift_または_IO.async_で明示的に要求しない限り、別のOSスレッドまたは非同期で実行することを保証しません。

いつでもスレッドの切り替えを保証できます。 _(IO.shift *> myIO).unsafeRunAsyncAndForget()_。これは、myIOが_val myIO_であるか_def myIO_であるかにかかわらず、要求されるまで実行されないためです。

ただし、ブロック操作を非ブロックに魔法のように変換することはできません。 FutureIOもそれは不可能です。


猫効果における非同期と同時のポイントは何ですか?なぜ分離されているのですか?

AsyncConcurrent(およびSync)は型クラスです。これらは、プログラマが_cats.effect.IO_にロックされるのを避け、monix TaskやScalaz 8 ZIOなどの選択したものをサポートするAPI、または_OptionT[Task, *something*]_などのモナドトランスフォーマタイプさえ提供できるように設計されています。 fs2、monix、http4sなどのライブラリは、それらを使用して、何を使用するかについてより多くの選択肢を提供します。

ConcurrentAsyncの上に追加のものを追加します。それらの最も重要なものは_.cancelable_と_.start_です。これらはFutureと直接の類似点はありません。これはキャンセルをまったくサポートしていないためです。

_.cancelable_は_.async_のバージョンであり、ラップしている操作をキャンセルするロジックも指定できます。一般的な例はネットワークリクエストです。結果に興味がなくなった場合は、サーバーの応答を待たずに、リクエストを中止することができます。応答の読み取りでソケットや処理時間を無駄にしないでください。直接使用することは決してないかもしれませんが、場所があります。

しかし、キャンセルできない場合のキャンセル可能な操作とは何でしょうか?ここで重要なのは、操作をそれ自体からキャンセルできないことです。他の誰かがその決定を行わなければなりません、そしてそれは起こります同時に操作自体(これは型クラスがその名前を取得する場所です)。それが_.start_の出番です。要するに、

_.start_は緑のスレッドの明示的な分岐です。

_someIO.start_を実行することは、val t = new Thread(someRunnable); t.start()を実行することと似ていますが、今は緑色です。そしてFiberは基本的にThread AP​​Iのストリップバージョンです:_.join_を実行できますが、これはThread#join()に似ていますが、OSスレッドをブロックしません。および_.cancel_、これは.interrupt()の安全なバージョンです。


グリーンスレッドをフォークする他の方法があることに注意してください。たとえば、並列操作を行う場合:

_val ids: List[Int] = List.range(1, 1000)
def processId(id: Int): IO[Unit] = ???
val processAll: IO[Unit] = ids.parTraverse_(processId)
_

すべてのIDの処理をフォークしてグリーンスレッドにしてから、それらすべてを結合します。または_.race_を使用:

_val fetchFromS3: IO[String] = ???
val fetchFromOtherNode: IO[String] = ???

val fetchWhateverIsFaster = IO.race(fetchFromS3, fetchFromOtherNode).map(_.merge)
_

フェッチを並行して実行し、最初の結果を完了させ、遅いフェッチを自動的にキャンセルします。したがって、_.start_を実行し、Fiberを使用することは、より多くのグリーンスレッドをフォークする唯一の方法ではなく、最も明示的なものだけです。そして、それは答えます:

IO緑のスレッドですか?はいの場合、猫効果にファイバーオブジェクトがあるのはなぜですか?私が理解しているように、ファイバーは緑のスレッドですが、ドキュメントではIOを緑のスレッドと見なすことができると主張しています。 。

  • IOはグリーンスレッドのようなものです。つまり、OSスレッドのオーバーヘッドなしに多くのスレッドを並行して実行でき、for-comprehensionのコードは、結果の計算がブロックされているかのように動作します。

  • Fiberは、明示的にforkされた(完了を待つかキャンセルする)緑のスレッドを制御するためのツールです。

29
Oleg Pyzhcov