Kotlin:将“正常”功能转换为阻塞挂起功能会对性能产生影响吗?

时间:2019-04-06 09:11:39

标签: asynchronous kotlin async-await coroutine kotlin-coroutines

我有一个看起来像这样的函数:

fun <R> map(block: (T) -> R): Result<R> { ... }

,我想制作一个暂停版本:

suspend fun <R> mapAsync(block: suspend (T) -> R): Result<R> { ... }

两个主体中的逻辑相同,但是一个挂起,另一个挂起。

我不想有这种重复的逻辑。我发现要执行此操作的唯一方法是将map函数调用到mapAsync函数,然后将结果包装在runBlocking中:

fun <R> map(block: (T) -> R): Result<R> =
    runBlocking { mapAsync { block(it) } }

所以我有两个问题:

  1. 采用“正常”函数,将其作为suspend参数传递,然后阻塞直到结果完成,是否有性能方面的考虑?
    • 根据我所读的内容,听起来好像初始线程一直在“暂停”块内“进行工作”,直到到达第一个暂停点为止。然后,将继续放入等待队列,并且初始线程可以自由执行其他工作。
    • 但是,在这种情况下,没有任何“真实的”挂起点,因为实际的功能只是(T) -> R,尽管我不知道编译器是否可以告诉您。
    • 我担心此设置实际上正在利用池中的另一个线程,该线程只是通知我的第一个线程唤醒...
  2. 是否有更好的方法来使一组暂停和未暂停的函数使用相同的代码?

2 个答案:

答案 0 :(得分:3)

您遇到了臭名昭著的“ colored function”问题。这两个世界确实是分开的,尽管您可以添加一个统一它们的表面层,但您不能以零性能成本获得它。这是如此基础,即使假设您的suspend块实际上从未挂起,并且包装层利用了该假设并且甚至没有在其上使用runBlocking,您也会静止付出“准备暂停”的代价。不过,价格并不昂贵:这意味着每个suspend fun调用都会创建一个小对象,以容纳通常驻留在线程的本机调用堆栈中的数据。在您的情况下,只有外部块是可挂起的,所以这只是一个这样的对象。

runBlocking在您调用它的线程上运行协程,除非协程自身挂起,否则它将在同一线程上同步完成。因此,在suspend块中有一些同步代码的情况下,线程协调不会带来额外的性能损失。

如果协程确实挂起了自己,那么将必须有一些外部工作线程来对允许协程恢复的事件做出反应,并且该线程与原始{{1 }}线程。这是存在或不存在协程的基本机制。

答案 1 :(得分:1)

您的方法是正确的,runBlocking经过专门设计,可以用作阻塞操作和非阻塞操作之间的连接。从文档中:

  

运行新的协程并中断当前线程,直到其   完成。协程不应该使用此功能。 是   旨在将常规阻止代码桥接到编写的库   ,用于悬浮液,用于主要功能和测试。

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html

还进一步阅读: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/basics.md#bridging-blocking-and-non-blocking-worlds

还有Roman Elizarov的一些有趣的视频:

https://youtu.be/_hfBv0a09Jc

https://youtu.be/a3agLJQ6vt8