有人可以解释为什么人们应该使用协程吗?是否有一些协程代码示例显示了比常规Java并发代码(没有神奇的delay()函数,没有人在生产中使用delay()
)的完成时间更短的示例?
在我的个人示例中,协程(第1行)与Java代码(第2行)相反。也许我做错了什么?
示例:
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)
}
}
其他问题:
为什么我应该创建此协程包装器await()
?似乎并不能改善代码的协程版本,否则get()
方法会在inappropriate blocking method call
上抱怨吗?
答案 0 :(得分:5)
协程的目标不是“更好的完成时间”。坦白说,成功的目标是使协程更容易使用。
也就是说,您在代码中所做的根本不是 比较两个替代方法速度的好方法。比较Java中事物的速度并获得逼真的结果极其困难,因此在尝试使用How do I write a correct micro-benchmark in Java?之前,应至少阅读它。您目前试图比较两段Java代码的方式将骗您关于代码的实际性能行为。
要回答您的其他问题,答案是您不应创建该await
方法。无论协程代码在get()
中,还是不使用java.util.concurrent.Future
或suspendCancellableCoroutine
,都应将它们与协同程序代码一起使用。如果您想使用CompletableFuture
,请使用the provided library从协程代码与之交互。
答案 1 :(得分:2)
这是我用于基准测试的代码的清理版本。注意,我从测量的代码中删除了print
,因为打印本身是一项重量级的操作,涉及互斥锁,JNI,阻塞输出流等。相反,我更新了一个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() }
}
我的结果是案例一号是6.5秒,而案例二是7秒。这是7%的差异,并且可能是针对此确切场景的特定情况,并不是您通常会认为这两种方法之间的差异。
在基于CompletionStage
的程序中选择协程的原因绝对不是那7%,而是便利性的巨大差异。要了解我的意思,我邀请您通过调用main
而不使用computeAsync
来重写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()
答案 2 :(得分:0)
切换到此kotlinx-coroutines-jdk8库并将sleep(1)添加到我的getText()
函数中
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)
"."
}
}
我使协程版本比Java版本快!!!显然,当有一些延迟时,这个额外的协程层变得合理。
答案 3 :(得分:0)
我的2个版本的compute
方法,没有重写方法签名。我想我明白你的意思。使用协程,我们以熟悉的顺序样式编写复杂的并行代码。但是协程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!!
}
}