はじめに
Kotlinを学習する中で、新しく学んだことを整理いたします。
ルーチンとコルーチンの違い
-
ルーチンは一度開始すると、終了するまで止まりません。
-
ルーチンは一度終了すると、ルーチン内の情報が消えてしまいます。
-
コルーチンは中断され、再開することが可能です。
-
中断されても、ルーチン内の情報は消えません。
package coroutine
import kotlinx.coroutines.*
suspend fun newRoutine(){ // 他のsuspend関数を呼び出すことができます。
val num1 = 1
val num2 = 2
yield() // スレッドを譲ります。中断と再開。
printWithThread("${num1 + num2}") // ルーチンが中断された後、メモリ上に保存されて再開されます。
}
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
printWithThread("START")
launch { // 戻り値のないコルーチンを作成します。
newRoutine() // すぐには実行されません。
}
yield() // newRoutineで譲渡され、ENDが呼び出されます。
printWithThread("END")
}
fun printWithThread(str: Any){
println("[${Thread.currentThread().name}] $str")
}
結果
runBlockingを実行して1番目のコルーチンを実行し、launchで2番目のコルーチンを実行します。しかし、yieldの宣言により途中でメモリに保存された状態で再開されます。
[main @coroutine#1] START
[main @coroutine#1] END
[main @coroutine#2] 3
プロセス、スレッド、そしてコルーチン
スレッドはプロセスに従属しています。スレッドがプロセスを変更することはできません。
コルーチンのコードが実行されるためには、スレッドが必要です。
中断後に再開される際には、別のスレッドに割り当てられることがあります。
コルーチンのコードはどのスレッドでも実行できます。
プロセスは独立したメモリを持ち、コンテキストスイッチング時にはすべてのデータが変更されます。
スレッドはヒープエリアを共有し、スタックエリアだけが置き換えられます。
コルーチンが同じスレッドで実行される場合、コンテキストスイッチングが異なります。
メモリの置き換えは発生しません。 -> 並行性
コルーチンは自らの位置を譲ることができます。非プリエンプティブ
runBlocking
新しいコルーチンを作成し、ルーチンとコルーチンを接続します。
スレッドをブロッキングします。
fun main() { // コルーチンの世界へ接続します。
runBlocking {
printWithThread("START")
launch {
delay(2_000L) // yield
printWithThread("LAUNCH END")
}
}
printWithThread("END")
}
結果
[main @coroutine#1] START
[main @coroutine#2] LAUNCH END
[main] END
launch
戻り値のないコードを実行します。
Jobオブジェクトを返すことができます。
start
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val job = launch(start = CoroutineStart.LAZY) {
printWithThread("Hello launch")
}
delay(1000)
job.start()
}
cancel
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val job = launch {
(1..5).forEach{
printWithThread(it)
delay(300)
}
}
delay(1000)
job.cancel()
}
[main @coroutine#2] 1
[main @coroutine#2] 2
[main @coroutine#2] 3
[main @coroutine#2] 4
join
awaitに似ています。
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val job1 = launch {
delay(1000)
printWithThread("job 1")
}
job1.join()
val job2 = launch {
delay(1000)
printWithThread("job 2")
}
job2.start()
}
async
結果を返すことができます。Promiseのようなものです。
複数のAPIを一度に呼び出すことができます。
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val job1 = async {
3 + 5
}
val eight = job1.await()
print(eight)
}
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val time = measureTimeMillis {
val job2 = async { apiCall2() }
val job1 = async { apiCall1() }
printWithThread(job1.await() + job2.await())
}
printWithThread("所要時間: $time ms")
}
/*
fun main(): Unit = runBlocking { // コルーチンの世界へ接続します。
val job1 = async { apiCall1() }
val job2 = async { apiCall2(job1.await()) }
}
suspend fun apiCall1(): Int{
delay(1000L)
return 1
}
suspend fun apiCall2(num : Int): Int{
delay(1000L)
return 2 + num
}
*/
suspend fun apiCall1(): Int{
delay(1000L)
return 1
}
suspend fun apiCall2(): Int{
delay(1000L)
return 2
}
/*
[main @coroutine#1] 3
[main @coroutine#1] 所要時間: 1065 ms
*/
Coroutine.LAZY
CoroutineStart.LAZYは、実際にコルーチンの実行が完了するまで待機するため、時間がかかります。
val job2 = async(start= CoroutineStart.LAZY) { apiCall2() }