Kotlin 协程比线程花费更长的时间
Kotlin coroutines takes longer than threads
我是 Kotlin 和协程的新手,我正在尝试了解协程 API,所以我很可能做错了什么。所以我有一些对象的列表,我正在尝试对这些对象中的每一个应用一些 long-运行 处理。
val listOfFoos = listOf(Foo(1), ..., Foo(n))
listOfFoos.forEach { longRunningJob(it) }
fun longRunningJob(foo: Foo) {
runBlocking{
delay(2000) //hardcoded delay for testing
}
//do something else
}
当然这是 运行 它并发的完美候选者,所以这里它使用了很好的旧线程:
listOfFoos.map { thread(start = true) { longRunningJob(it) } }.forEach { it.join() }
当我使用 measureTimeMillis
测量它的执行时间时,它给了我大约 2 秒,这看起来非常好,因为每个 longRunningJob
并行运行。
但是协程要好得多,因为它没有上下文切换线程那样的开销。所以这是我使用协程的实现:
val deferredResults =
listOfFoos.map { GlobalScope.async { longRunningJob(it) } }
runBlocking {
deferredResults.awaitAll()
}
但是这个实现在大约 4 秒内完成了执行,这根本不是我所期望的,如果我向列表添加更多元素,执行时间也会增加。
那么我做错了什么?
执行此代码所需的时间取决于用于计算的线程数。您的线程示例没有定义界限,并且会生成与作业一样多的线程。另一方面,协程示例将所有任务分派给内部使用 Dispatchers.Default
线程池的 GlobalScope
。此池是有限的:
The default CoroutineDispatcher that is used by all standard builders like launch, async, etc. if no dispatcher nor any other ContinuationInterceptor is specified in their context.
It is backed by a shared pool of threads on JVM. By default, the maximal number of threads used by this dispatcher is equal to the number CPU cores, but is at least two.
假设您有 4 个内核。 运行 具有 4 个作业的代码将导致约 2 秒 运行 时间,因为所有 运行 都是并行的(注意并发 <> 并行性)。但是一旦你有超过 4 个任务,就必须等到第一个任务之一完成,因为在任何时候只有 4 个任务可以同时 运行。
您可以将调度程序池更改为具有更多线程的调度程序池:
GlobalScope.async(Dispatchers.IO)
请注意 delay
是长时间 运行ning 任务的坏例子。它不会阻塞调用者线程,因为它是一个真正的挂起函数,只会暂停协程。您实际上可以 运行 您的代码 main
完全:
runBlocking {
val deferredResults =
(0..10).map { async(Dispatchers.IO) { longRunningJob() } }
deferredResults.awaitAll()
}
运行阻塞是协程函数。它的确切作用是'runs the code in the main thread/calling thread'。所以它不是为 运行 事物创建并行线程。
为了 运行 您的异步代码,您应该使用启动函数而不是 运行 阻塞。它 运行 在 Dispatchers.Default 共享线程池上。
GlobalScope.launch {
delay(2000);
}
我是 Kotlin 和协程的新手,我正在尝试了解协程 API,所以我很可能做错了什么。所以我有一些对象的列表,我正在尝试对这些对象中的每一个应用一些 long-运行 处理。
val listOfFoos = listOf(Foo(1), ..., Foo(n))
listOfFoos.forEach { longRunningJob(it) }
fun longRunningJob(foo: Foo) {
runBlocking{
delay(2000) //hardcoded delay for testing
}
//do something else
}
当然这是 运行 它并发的完美候选者,所以这里它使用了很好的旧线程:
listOfFoos.map { thread(start = true) { longRunningJob(it) } }.forEach { it.join() }
当我使用 measureTimeMillis
测量它的执行时间时,它给了我大约 2 秒,这看起来非常好,因为每个 longRunningJob
并行运行。
但是协程要好得多,因为它没有上下文切换线程那样的开销。所以这是我使用协程的实现:
val deferredResults =
listOfFoos.map { GlobalScope.async { longRunningJob(it) } }
runBlocking {
deferredResults.awaitAll()
}
但是这个实现在大约 4 秒内完成了执行,这根本不是我所期望的,如果我向列表添加更多元素,执行时间也会增加。
那么我做错了什么?
执行此代码所需的时间取决于用于计算的线程数。您的线程示例没有定义界限,并且会生成与作业一样多的线程。另一方面,协程示例将所有任务分派给内部使用 Dispatchers.Default
线程池的 GlobalScope
。此池是有限的:
The default CoroutineDispatcher that is used by all standard builders like launch, async, etc. if no dispatcher nor any other ContinuationInterceptor is specified in their context.
It is backed by a shared pool of threads on JVM. By default, the maximal number of threads used by this dispatcher is equal to the number CPU cores, but is at least two.
假设您有 4 个内核。 运行 具有 4 个作业的代码将导致约 2 秒 运行 时间,因为所有 运行 都是并行的(注意并发 <> 并行性)。但是一旦你有超过 4 个任务,就必须等到第一个任务之一完成,因为在任何时候只有 4 个任务可以同时 运行。
您可以将调度程序池更改为具有更多线程的调度程序池:
GlobalScope.async(Dispatchers.IO)
请注意 delay
是长时间 运行ning 任务的坏例子。它不会阻塞调用者线程,因为它是一个真正的挂起函数,只会暂停协程。您实际上可以 运行 您的代码 main
完全:
runBlocking {
val deferredResults =
(0..10).map { async(Dispatchers.IO) { longRunningJob() } }
deferredResults.awaitAll()
}
运行阻塞是协程函数。它的确切作用是'runs the code in the main thread/calling thread'。所以它不是为 运行 事物创建并行线程。
为了 运行 您的异步代码,您应该使用启动函数而不是 运行 阻塞。它 运行 在 Dispatchers.Default 共享线程池上。
GlobalScope.launch {
delay(2000);
}