简体   繁体   English

Kotlin 协程与 CompletableFuture

[英]Kotlin Coroutines vs CompletableFuture

Can anybody explain me why people should use coroutines?谁能解释我为什么人们应该使用协程? Is there some coroutine code example which shows better completion time against regular java concurrent code (without magical delay() function, nobody uses delay() in production) ?是否有一些协程代码示例显示了比常规 java 并发代码更好的完成时间(没有神奇的延迟() function,没有人在生产中使用delay()

In my personal example coroutines(line 1) are suck against java code(line 2).在我的个人示例中,协程(第 1 行)对 java 代码(第 2 行)很不利。 Maybe i did something wrong?也许我做错了什么?

Example:例子:

import kotlinx.coroutines.*
import java.time.Instant
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future

@ExperimentalCoroutinesApi
fun main() = runBlocking {
    val begin = Instant.now().toEpochMilli()
    val jobs = List(150_000) {
        GlobalScope.launch { print(getText().await()) } // :1
//        CompletableFuture.supplyAsync { "." }.thenAccept { print(it) } // :2
    }
    jobs.forEach { it.join() }
    println(Instant.now().toEpochMilli() - begin)
}

fun getText(): Future<String> {
    return CompletableFuture.supplyAsync {
        "."
    }
}

@ExperimentalCoroutinesApi
suspend fun <T> Future<T>.await(): T = suspendCancellableCoroutine { cont ->
    cont.resume(this.get()) {
        this.cancel(true)
    }
}

Additional question:附加问题:

Why i should create this coroutine wrapper await() ?为什么我应该创建这个协程包装器await() It seems does not improve coroutine version of code otherwise get() method complains on inappropriate blocking method call ?似乎没有改进代码的协程版本,否则get()方法会抱怨inappropriate blocking method call

The goal of coroutines is not "better completion time."协程的目标不是“更好的完成时间”。 The goal -- at which it succeeds quite well, honestly -- is that coroutines are easier to use .老实说,它成功的目标是协程更易于使用

That said, what you've done in your code is not at all a good way to compare the speed of two alternate approaches.也就是说,您在代码中所做的根本不是比较两种替代方法速度的好方法。 Comparing the speed of things in Java and getting realistic results is extremely hard , and you should read How do I write a correct micro-benchmark in Java?在 Java 中比较事物的速度并获得真实的结果非常困难,您应该阅读如何在 Java 中编写正确的微基准测试? at a minimum before attempting it.至少在尝试之前。 The way you are currently attempting to compare two pieces of Java code will lie to you about the realistic performance behavior of your code.您当前尝试比较两段 Java 代码的方式将欺骗您代码的实际性能行为。

To answer your additional question, the answer is that you should not create that await method.要回答您的其他问题,答案是您不应该创建该await方法。 You should not use get() -- or java.util.concurrent.Future -- with coroutine code, whether it's in suspendCancellableCoroutine or otherwise.你不应该使用get() - 或java.util.concurrent.Future - 与协程代码,无论它是在suspendCancellableCoroutine还是其他。 If you want to use a CompletableFuture , use the provided library to interact with it from coroutine code.如果您想使用CompletableFuture ,请使用提供的库从协程代码中与之交互。

After switching to this kotlinx-coroutines-jdk8 library and adding sleep(1) to my getText() function切换到这个kotlinx-coroutines-jdk8库并将 sleep(1) 添加到我的getText() function

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.Thread.sleep
import java.time.Instant
import java.util.concurrent.CompletableFuture

fun main() = runBlocking {
    val begin = Instant.now().toEpochMilli()
    val jobs = List(150_000) {
        GlobalScope.launch { print(getText().await()) } // :1
//        getText().thenAccept { print(it) } // :2
    }
    jobs.forEach { it.join() }
    println(Instant.now().toEpochMilli() - begin)
}

fun getText(): CompletableFuture<String> {
    return CompletableFuture.supplyAsync {
        sleep(1)
        "."
    }
}

i made coroutine version faster than java version.!!我制作的协程版本比 java 版本快!! Apparently this additional coroutine layer becomes justified when there is some delay.显然,当有一些延迟时,这个额外的协程层变得合理。

Here's a cleaned-up version of your code that I used for benchmarking.这是我用于基准测试的代码的清理版本。 Note I removed print from the measured code because printing itself is a heavyweight operation, involving mutexes, JNI, blocking output streams, etc. Instead I update a volatile variable.注意我从测量代码中删除了print ,因为打印本身是一个重量级操作,涉及互斥锁、JNI、阻塞 output 流等。相反,我更新了一个 volatile 变量。

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.Thread.sleep
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.concurrent.TimeUnit.NANOSECONDS

@Volatile
var total = 0

@ExperimentalCoroutinesApi
fun main() = runBlocking {
    println("Warmup")
    measure(20_000)
    println("Measure")
    val begin = System.nanoTime()
    measure(40_000)
    println("Completed in ${NANOSECONDS.toMillis(System.nanoTime() - begin)} ms")
}

fun getText(): CompletableFuture<Int> {
    return CompletableFuture.supplyAsync {
        sleep(1)
        1
    }
}

suspend fun measure(count: Int) {
    val jobs = List(count) {
        GlobalScope.launch { total += getText().await() } // :1
//        getText().thenAccept { total += it } // :2
    }
    jobs.forEach { it.join() }
}

My result is 6.5 seconds for case number one vs. 7 seconds for case number two.我的结果是第一个案例为 6.5 秒,而第二个案例为 7 秒。 That's a 7% difference and it's probably very specific to this exact scenario, not something you'll generally see as a difference between the two approaches.那是 7% 的差异,它可能非常特定于这个确切的场景,而不是你通常认为这两种方法之间的差异。

The reason to choose coroutines over CompletionStage -based programming is definitely not about those 7%, but about the massive difference in convenience.选择协程而不是基于CompletionStage的编程的原因绝对不是关于那 7%,而是关于便利性的巨大差异。 To see what I mean, I invite you to rewrite the main function by calling just computeAsync , without using future.await() :为了明白我的意思,我邀请您通过调用computeAsync来重写main的 function ,而不使用future.await()

suspend fun main() {
    try {
        if (compute(1) == 2) {
            println(compute(4))
        } else {
            println(compute(7))
        }
    } catch (e: RuntimeException) {
        println("Got an error")
        println(compute(8))
    }
}

fun main_no_coroutines() {
    // Let's see how it might look!
}

fun computeAsync(input: Int): CompletableFuture<Int> {
    return CompletableFuture.supplyAsync {
        sleep(1)
        if (input == 7) {
            throw RuntimeException("Input was 7")
        }
        input % 3
    }
}

suspend fun compute(input: Int) = computeAsync(input).await()

My 2 versions of compute method without rewriting methods signature.我的 2 个版本的compute方法没有重写方法签名。 I think i've got your point.我想我明白你的意思。 With coroutines we write complex parallel code in familiar sequential style.使用协程,我们以熟悉的顺序样式编写复杂的并行代码。 But coroutine await wrapper does not make this work due suspending technic, it just implement same logic that i did.但是协程await包装器由于暂停技术而无法完成这项工作,它只是实现了与我相同的逻辑。

import java.lang.Thread.sleep
import java.util.concurrent.CompletableFuture

fun main() {
    try {
        if (compute(1) == 2) {
            println(compute(4))
        } else {
            println(compute(7))
        }
    } catch (e: RuntimeException) {
        println("Got an error")
        println(compute(8))
    }
}

fun compute(input: Int): Int {
    var exception: Throwable? = null
    val supplyAsync = CompletableFuture.supplyAsync {
        sleep(1)
        if (input == 7) {
            throw RuntimeException("Input was 7")
        }
        input % 3
    }.exceptionally {
        exception = it
        throw it
    }
    while (supplyAsync.isDone.not()) {}
    return if (supplyAsync.isCompletedExceptionally) {
        throw exception!!
    } else supplyAsync.get()
}

fun compute2(input: Int): Int {
    try {
        return CompletableFuture.supplyAsync {
            sleep(1)
            if (input == 7) {
                throw RuntimeException("Input was 7")
            }
            input % 3
        }.get()
    } catch (ex: Exception) {
        throw ex.cause!!
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM