web-dev-qa-db-ja.com

Kotlinのスレッドとコルーチンの違い

Kotlinには、コルーチンの別の言語実装とは異なる特定の言語実装がありますか?

  • コルーチンが軽量スレッドのようなものであることはどういう意味ですか?
  • 違いはなんですか?
  • Kotlinコルーチンは実際に並行して/同時に実行されていますか?
  • マルチコアシステムでも、常に実行されているコルーチンは1つだけです(正しいですか?)

ここで私は100000個のコルーチンを開始していますが、このコードの背後で何が起こりますか?

for(i in 0..100000){
   async(CommonPool){
    //run long running operations
  }
}
50

JVMでのみコルーチンを使用したため、JVMバックエンドについて説明します。KotlinNativeとKotlin JavaScriptもありますが、Kotlinのこれらのバックエンドは私の範囲外です。

それでは、Kotlinコルーチンを他の言語のコルーチンと比較することから始めましょう。基本的に、コルーチンにはスタックレスとスタックフルの2種類があることを知っておく必要があります。 Kotlinはスタックレスコルーチンを実装しています。これは、コルーチンに独自のスタックがないこと、およびコルーチンが実行できることを少し制限することを意味します。適切な説明を読むことができます こちら

例:

  • スタックレス:C#、Scala、Kotlin
  • Stackful:Quasar、Javaflow

コルーチンが軽量スレッドのようなものであることはどういう意味ですか?

これは、Kotlinのコルーチンが独自のスタックを持たず、ネイティブスレッドにマップせず、プロセッサーのコンテキスト切り替えを必要としないことを意味します。

違いはなんですか?

スレッド-プリエンプティブマルチタスク。 ( 通常 )。コルーチン-協調的にマルチタスク。

スレッド-OSによって管理されます(通常)。コルーチン-ユーザーが管理します。

Kotlinのコルーチンは実際に並行して/並行して実行されていますか?

それは、各コルーチンを独自のスレッドで実行できるか、1つのスレッドまたはいくつかの固定スレッドプールですべてのコルーチンを実行できるかによって異なります。

コルーチンの実行方法の詳細 here

マルチコアシステムであっても、常に実行されているコルーチンは1つだけです(正しいですか?)

いいえ、前の回答を参照してください。

ここで、100000個のコルーチンを開始していますが、このコードの背後で何が起こるのでしょうか

実際、それは依存します。ただし、次のコードを書くと仮定します。

fun main(args: Array<String>) {
    for (i in 0..100000) {
        async(CommonPool) {
            delay(1000)
        }
    }
}

このコードは即座に実行されます。

async呼び出しからの結果を待つ必要があるためです。

これを修正しましょう:

fun main(args: Array<String>) = runBlocking {
    for (i in 0..100000) {
        val job = async(CommonPool) {
            delay(1)
            println(i)
        }

        job.join()
    }
}

このプログラムを実行すると、kotlinはContinuationの2 * 100000インスタンスを作成します。これは数十MBのRAMを必要とし、コンソールには1〜100000の数字が表示されます。

そのため、このコードを次のように書き換えましょう。

fun main(args: Array<String>) = runBlocking {

    val job = async(CommonPool) {
        for (i in 0..100000) {
            delay(1)
            println(i)
        }
    }

    job.join()
}

私たちは今何を達成していますか? Continuationのインスタンスを100001個だけ作成しましたが、これははるかに優れています。

作成された各Continuationは、CommonPool(ForkJoinPoolの静的インスタンス)でディスパッチおよび実行されます。

44
Ruslan

コルーチンは軽量スレッドのようなものですか?

コルーチンは、スレッドのように、他のコルーチン(スレッド)と同時に実行される一連のアクションを表します。

違いはなんですか?

スレッドは、対応するOS(オペレーティングシステム)のネイティブスレッドに直接リンクされ、かなりの量のリソースを消費します。特に、スタックのために多くのメモリを消費します。そのため、10万スレッドを作成することはできません。メモリが不足する可能性があります。スレッド間の切り替えにはOSカーネルディスパッチャーが関係し、CPUサイクルの消費という点ではかなり高価な操作です。

一方、コルーチンは、純粋にユーザーレベルの言語の抽象化です。ネイティブリソースを結び付けず、最も単純な場合、JVMヒープ内の比較的小さなオブジェクトを1つだけ使用します。そのため、10万個のコルーチンを簡単に作成できます。コルーチン間の切り替えには、OSカーネルはまったく関与しません。通常の関数を呼び出すのと同じくらい安価です。

Kotlinのコルーチンは実際に並行して/同時に実行されていますか?マルチコアシステムであっても、常に実行されているコルーチンは1つだけです(正しいですか?)

コルーチンは実行中または一時停止中のいずれかです。中断されたコルーチンは特定のスレッドに関連付けられていませんが、実行中のコルーチンはいくつかのスレッドで実行されます(OSプロセス内で何かを実行する唯一の方法はスレッドを使用することです)。異なるコルーチンがすべて同じスレッドで実行されるか(したがって、マルチコアシステムで単一のCPUのみを使用するか)、異なるスレッドで実行されるか(したがって複数のCPUを使用するか)は、純粋にコルーチンを使用するプログラマーの手にあります。

Kotlinでは、コルーチンのディスパッチはコルーチンコンテキストを介して制御されます。それについての詳細は kotlinx.coroutinesのガイド で読むことができます。

ここで、100000個のコルーチンを開始していますが、このコードの背後で何が起こるのでしょうか

_kotlinx.coroutines_プロジェクト(オープンソース)のlaunch関数とCommonPoolコンテキストを使用していると仮定すると、それらのソースコードをここで調べることができます。

launchは新しいコルーチンを作成しますが、CommonPoolは複数のスレッドを使用するForkJoinPool.commonPool()にコルーチンをディスパッチし、この例では複数のCPUで実行します。

_{...}_でのlaunch呼び出しに続くコードは、中断ラムダと呼ばれます。それが何であり、どのように中断されたラムダと関数が実装(コンパイル)されているか、標準ライブラリ関数とstartCoroutinessuspendCoroutineCoroutineContextのようなクラスは対応するで説明されています- Kotlinコルーチン設計ドキュメント

51
Roman Elizarov