【问题标题】:Kotlin coroutines takes longer than threadsKotlin 协程比线程花费更长的时间
【发布时间】:2019-08-24 18:52:57
【问题描述】:

我是 Kotlin 和协程的新手,我正在尝试理解协程 API,所以很可能我做错了什么。所以我有一些对象的列表,我正在尝试对这些对象中的每一个应用一些长时间运行的处理。

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 秒内完成了执行,这根本不是我所期望的,如果我向列表中添加更多元素,执行时间也会增加。

那么我在这里做错了什么?

【问题讨论】:

    标签: kotlin concurrency coroutine kotlin-coroutines


    【解决方案1】:

    执行此代码所需的时间取决于用于计算的线程数。您的线程示例未定义绑定并产生与您的作业一样多的线程。另一方面,协程示例将所有任务分派到GlobalScope,后者在内部使用Dispatchers.Default 线程池。此池有限:

    如果在上下文中未指定调度程序或任何其他 ContinuationInterceptor,则所有标准构建器(如启动、异步等)使用的默认 CoroutineDispatcher。

    它由 JVM 上的共享线程池提供支持。默认情况下,此调度程序使用的最大线程数等于 CPU 内核数,但至少为两个。

    假设您有 4 个内核。使用 4 个作业运行代码将导致大约 2 秒的运行时间,因为一切都是并行运行的(注意并发 并行性)。但是,一旦您有超过 4 个任务,就必须等待第一个任务完成,因为任何时候只有 4 个任务可以同时运行。

    您可以将调度程序池更改为具有更多线程的调度程序池:

    GlobalScope.async(Dispatchers.IO)
    

    请注意,delay 是长时间运行任务的不良示例。它不会阻塞调用者线程,因为它是一个真正的挂起函数,只会暂停协程。您实际上可以完全在 main 上运行您的代码:

    runBlocking {
        val deferredResults =
            (0..10).map { async(Dispatchers.IO) { longRunningJob() } }
        deferredResults.awaitAll()
    }
    

    【讨论】:

    • 感谢您的详细回答。关于delay,包裹在runBlocking中,不让它阻塞吗?
    • 不,这不是“主动等待”,协程只是暂停了给定的持续时间。
    • 最好的替换方法是什么?据我了解,Thread.sleep 会阻塞整个线程,所有协程都可能被卡住,对吗?
    • “替换”是什么意思?使用协程,您不会使用Thread.sleep。但这适用于您的示例。它将完全阻塞一个线程,即您正在使用它的线程
    • 我的意思是替换,所以它将是长时间运行任务的一个很好的例子。它会阻塞线程从而阻塞在这个线程上运行的所有协程,还是只会阻塞一个协程?
    【解决方案2】:

    runBlocking 是一个协程函数。它的确切作用是“在主线程/调用线程中运行代码”。所以它不是创建并行线程来运行的东西。

    要异步运行代码,您应该使用启动函数而不是 runBlocking。它在Dispatchers.Default 共享线程池上运行。

    GlobalScope.launch {
       delay(2000);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-01
      • 1970-01-01
      • 2014-04-23
      • 1970-01-01
      • 2017-06-29
      • 2014-05-03
      • 2019-11-22
      • 2012-02-18
      相关资源
      最近更新 更多