在哪些方面,奔跑比悬浮更糟糕?

发布于 2025-01-25 02:45:27 字数 2563 浏览 1 评论 0原文

官方文档中很清楚地指出,Runblocking“不应从Coroutine使用”。我大致了解了这个想法,但是我试图找到一个示例,其中使用Runblocking而不是暂停功能会对性能产生负面影响。

因此,我创建了一个这样的示例:

import java.time.Instant
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.time.Duration.Companion.seconds

private val time = 1.seconds

private suspend fun getResource(name: String): String {
    log("Starting getting ${name} for ${time}...")
    delay(time)
    log("Finished getting ${name}!")
    return "Resource ${name}"
}

fun main(args: Array<String>) = runBlocking {
    val resources = listOf("A", "B")
        .map { async { getResource(it) } }
        .awaitAll()
    log(resources)
}

fun log(msg: Any) {
    val now = DateTimeFormatter.ISO_INSTANT.format(Instant.now())
    println("$now ${Thread.currentThread()}: $msg")
}

这给出了:

2022-04-29T15:52:35.943156Z Thread[main,5,main]: Starting getting A for 1s...
2022-04-29T15:52:35.945570Z Thread[main,5,main]: Starting getting B for 1s...
2022-04-29T15:52:36.947539Z Thread[main,5,main]: Finished getting A!
2022-04-29T15:52:36.948334Z Thread[main,5,main]: Finished getting B!
2022-04-29T15:52:36.949233Z Thread[main,5,main]: [Resource A, Resource B]

从我的理解中开始的预期输出getResource(a)开始了,当它到达delay> delay> delay时,它使控件退回了然后GetResource(B)开始了。然后他们俩都在一个线程中等待,当时间过去时,他们俩再次被执行 - 一秒钟内的一切都按预期。

因此,现在我想将其“打破”并替换为getResource

private fun getResourceBlocking(name: String): String = runBlocking {
    log("Starting getting ${name} for ${time}...")
    delay(time)
    log("Finished getting ${name}!")
    "Resource ${name}"
}

并从main方法来代替getResource

然后我再次得到:

2022-04-29T15:58:41.908015Z Thread[main,5,main]: Starting getting A for 1s...
2022-04-29T15:58:41.910532Z Thread[main,5,main]: Starting getting B for 1s...
2022-04-29T15:58:42.911661Z Thread[main,5,main]: Finished getting A!
2022-04-29T15:58:42.912126Z Thread[main,5,main]: Finished getting B!
2022-04-29T15:58:42.912876Z Thread[main,5,main]: [Resource A, Resource B]

所以在a完成之前,启动了b仍然只花了1秒钟。同时,似乎没有任何其他线程(所有内容都在thread [main,5,main]中)。 那么这是如何工作的呢?在async中如何调用阻止功能使其在单个线程中“同时”执行?

It's quite clearly stated in official documentation that runBlocking "should not be used from a coroutine". I roughly get the idea, but I'm trying to find an example where using runBlocking instead of suspend functions negatively impacts performance.

So I created an example like this:

import java.time.Instant
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.time.Duration.Companion.seconds

private val time = 1.seconds

private suspend fun getResource(name: String): String {
    log("Starting getting ${name} for ${time}...")
    delay(time)
    log("Finished getting ${name}!")
    return "Resource ${name}"
}

fun main(args: Array<String>) = runBlocking {
    val resources = listOf("A", "B")
        .map { async { getResource(it) } }
        .awaitAll()
    log(resources)
}

fun log(msg: Any) {
    val now = DateTimeFormatter.ISO_INSTANT.format(Instant.now())
    println("$now ${Thread.currentThread()}: $msg")
}

This gives the expected output of:

2022-04-29T15:52:35.943156Z Thread[main,5,main]: Starting getting A for 1s...
2022-04-29T15:52:35.945570Z Thread[main,5,main]: Starting getting B for 1s...
2022-04-29T15:52:36.947539Z Thread[main,5,main]: Finished getting A!
2022-04-29T15:52:36.948334Z Thread[main,5,main]: Finished getting B!
2022-04-29T15:52:36.949233Z Thread[main,5,main]: [Resource A, Resource B]

From my understanding getResource(A) was started and the moment it arrived at delay it gave the control back and then getResource(B) was started. Then they both waited in a single thread and when the time passed, they both were again executed - everything in one second as expected.

So now I wanted to "break" it a little and replaced getResource with:

private fun getResourceBlocking(name: String): String = runBlocking {
    log("Starting getting ${name} for ${time}...")
    delay(time)
    log("Finished getting ${name}!")
    "Resource ${name}"
}

and called it from the main method in place of getResource.

and then again I got:

2022-04-29T15:58:41.908015Z Thread[main,5,main]: Starting getting A for 1s...
2022-04-29T15:58:41.910532Z Thread[main,5,main]: Starting getting B for 1s...
2022-04-29T15:58:42.911661Z Thread[main,5,main]: Finished getting A!
2022-04-29T15:58:42.912126Z Thread[main,5,main]: Finished getting B!
2022-04-29T15:58:42.912876Z Thread[main,5,main]: [Resource A, Resource B]

So it still took only 1 second to run and B was started before A finished. At the same time there doesn't seem to be any additional threads spawned (everything is in Thread[main,5,main]).
So how does this work? How calling blocking functions in async makes it execute "concurrently" in a single thread anyway?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

病女 2025-02-01 02:45:27

您的推理是正确的,但是您意外地遇到了使用Runblocking()的特殊情况,该案例已故意优化以不降低性能。如果您在另一个dispatcher runblocking()中使用dispatcher-less runblocking(),则INNER runblocking()尝试重新使用事件循环由外部创建。因此,Inner Runblocking()实际上是在暂停而不阻止的情况下工作类似(但这不是100%准确)。

在实际情况下,外部Coroutine本身不会使用RunBlocking()或使用某些真实的调度员创建,您会看到降级性能。您可以使用此处替换外部代码:

val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

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

然后,正如您所期望的那样,资源被顺序加载。但是,即使有了此更改,getResource()仍然同时加载资源。

Your reasoning is correct, but you accidentally hit a very special case of using runBlocking(), which was intentionally optimized to not degrade the performance. If you use dispatcher-less runBlocking() inside another dispatcher-less runBlocking(), then the inner runBlocking() tries to re-use the event loop created by the outer one. So inner runBlocking() actually works similarly as it is suspending and not blocking (but this is not 100% accurate).

In a real case where the outer coroutine would not be itself created with runBlocking() or if you use some real dispatchers, you would see the degraded performance. You can replace the outer code with this:

val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

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

Then resources are loaded sequentially, as you probably expected. But even with this change, getResource() still loads resources concurrently.

手长情犹 2025-02-01 02:45:27

暂停的目标不是更快地完成工作。暂停的目标是在等待悬挂函数返回时不要阻止当前线程。

在您的示例中,当前线程是否被阻止都没关系,因为它在等待时没有做其他事情。

在带有UI的应用程序中,您通常会担心在UI线程(又称主线程)上阻塞,因为这会冻结应用程序。在UI线程被阻止时,没有什么可以动画,滚动或单击的。

如果您从主线程调用悬挂函数,则在等待悬挂函数返回时,主线程将不会被阻止。


您的示例中没有其他线程的原因是您使用的是Runblocking,该默认情况下在当前线程上运行,而无需在背景线程上进行工作。在实际应用程序中,您将从CoroutinesCope而不是Runblocking启动Coroutines。

The goal with suspending is not to finish the work faster. The goal with suspending is to not block the current thread while waiting for the suspend function to return.

In your example, it doesn't matter if the current thread is blocked or not because it's not doing anything else while waiting.

In an app with a UI, you are usually concerned about blocking on the UI thread (AKA main thread), because that will freeze the app. Nothing can be animated, scrolled, or clicked while the UI thread is blocked.

If you call a suspend function from the main thread, the main thread will not be blocked while it waits for the suspend function to return.


The reason no other threads are spawned in your example is that you are using runBlocking which runs on the current thread by default without doing its work on a background thread. In an actual application, you would be launching coroutines from a CoroutineScope rather than from a runBlocking.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文