Kotlin:将 "normal" 函数转换为阻塞挂起函数对性能有何影响?
Kotlin: Any performance impact on converting a "normal" function to a blocking suspend function?
我有一个看起来像这样的函数:
fun <R> map(block: (T) -> R): Result<R> { ... }
我想做一个暂停版本:
suspend fun <R> mapAsync(block: suspend (T) -> R): Result<R> { ... }
两个body的逻辑是一样的,只是一个suspends一个不suspends。
我不想有这种重复的逻辑。我发现这个工作的唯一方法是让 map
函数调用 mapAsync
函数,然后将结果包装在 runBlocking
:
中
fun <R> map(block: (T) -> R): Result<R> =
runBlocking { mapAsync { block(it) } }
所以我有两个问题:
- 采用 "normal" 函数,将其作为
suspend
参数传递,然后阻塞直到结果完成,是否有任何性能考虑?
- 根据我读到的内容,听起来初始线程将 "doing the work" 保持在挂起块内,直到它到达第一个挂起点。然后,continuation 被放入等待队列,初始线程可以自由执行其他工作。
- 然而,在这种情况下,没有任何 "real" 挂起点,因为实际函数只是
(T) -> R
,虽然我不知道编译器是否可以告诉它。
- 我担心此设置实际上正在使用池中的另一个线程,该线程只是通知我的第一个线程唤醒...
- 是否有更好的方法让暂停和非暂停功能集使用相同的代码?
您的方法是正确的,runBlocking
专门设计用于连接阻塞和非阻塞操作。来自文档:
Runs new coroutine and blocks current thread interruptibly until its
completion. This function should not be used from coroutine. It is
designed to bridge regular blocking code to libraries that are written
in suspending style, to be used in main functions and in tests.
以及 Roman Elizarov 的一些有趣视频:
您遇到了臭名昭著的“colored function”问题。这两个世界确实是分开的,虽然您可以添加一个表层来统一它们,但您不能以零性能成本获得它。这是非常基础的,即使假设您的 suspend
块实际上从未挂起,并且包装层利用了该假设并且甚至不在其上使用 runBlocking
,您仍将 付出"being ready to suspend"的代价。不过,代价并不大:这意味着每次 suspend fun
调用都创建一个小对象,该对象保存通常驻留在线程的本机调用堆栈中的数据。在您的情况下,只有外部块是可暂停的,所以这只是一个这样的对象。
runBlocking
在您调用协程的线程上运行协程,它将在同一线程上同步完成,除非它自行挂起。因此,在 suspend
块中有一些同步代码的情况不会因线程协调而受到额外的性能影响。
如果协程确实挂起,则必须有一些外部工作线程对允许协程恢复的事件做出反应,并且该线程与您的原始线程之间必须有一些协调 runBlocking
线程。这是有或没有协程的基本机制。
我有一个看起来像这样的函数:
fun <R> map(block: (T) -> R): Result<R> { ... }
我想做一个暂停版本:
suspend fun <R> mapAsync(block: suspend (T) -> R): Result<R> { ... }
两个body的逻辑是一样的,只是一个suspends一个不suspends。
我不想有这种重复的逻辑。我发现这个工作的唯一方法是让 map
函数调用 mapAsync
函数,然后将结果包装在 runBlocking
:
fun <R> map(block: (T) -> R): Result<R> =
runBlocking { mapAsync { block(it) } }
所以我有两个问题:
- 采用 "normal" 函数,将其作为
suspend
参数传递,然后阻塞直到结果完成,是否有任何性能考虑?- 根据我读到的内容,听起来初始线程将 "doing the work" 保持在挂起块内,直到它到达第一个挂起点。然后,continuation 被放入等待队列,初始线程可以自由执行其他工作。
- 然而,在这种情况下,没有任何 "real" 挂起点,因为实际函数只是
(T) -> R
,虽然我不知道编译器是否可以告诉它。 - 我担心此设置实际上正在使用池中的另一个线程,该线程只是通知我的第一个线程唤醒...
- 是否有更好的方法让暂停和非暂停功能集使用相同的代码?
您的方法是正确的,runBlocking
专门设计用于连接阻塞和非阻塞操作。来自文档:
Runs new coroutine and blocks current thread interruptibly until its completion. This function should not be used from coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
以及 Roman Elizarov 的一些有趣视频:
您遇到了臭名昭著的“colored function”问题。这两个世界确实是分开的,虽然您可以添加一个表层来统一它们,但您不能以零性能成本获得它。这是非常基础的,即使假设您的 suspend
块实际上从未挂起,并且包装层利用了该假设并且甚至不在其上使用 runBlocking
,您仍将 付出"being ready to suspend"的代价。不过,代价并不大:这意味着每次 suspend fun
调用都创建一个小对象,该对象保存通常驻留在线程的本机调用堆栈中的数据。在您的情况下,只有外部块是可暂停的,所以这只是一个这样的对象。
runBlocking
在您调用协程的线程上运行协程,它将在同一线程上同步完成,除非它自行挂起。因此,在 suspend
块中有一些同步代码的情况不会因线程协调而受到额外的性能影响。
如果协程确实挂起,则必须有一些外部工作线程对允许协程恢复的事件做出反应,并且该线程与您的原始线程之间必须有一些协调 runBlocking
线程。这是有或没有协程的基本机制。