【问题标题】:Android & Kotlin Coroutines: Is it possible to run out of threads?Android 和 Kotlin 协程:是否有可能耗尽线程?
【发布时间】:2019-04-13 15:44:46
【问题描述】:

如何确定我是否在 Android/Kotlin 中用完了线程? 我正在构建一个需要从远程 API 加载大量数据的应用程序。我在代码中添加日志以检查线程名称,我看到至少有 5 个 Worker 并行运行。该应用程序具有滑动刷新功能,如果我滑动太多,在一定数量的调用后,我会以某种方式丢失数据(尽管我没有收到来自服务器的错误响应)。我观察到我感兴趣的呼叫从一个工人开始,然后这个工人被另一个进程占用。然后该方法永远不会完成。我有点疑惑。请帮助您提出如何解决多线程问题的任何建议。将Dispatcher.IO 更改为Dispatcher.Default 在行为上没有任何大的不同。 我可以将所有网络调用一个接一个(以顺序方式) - 即使我刷卡刷新 100 次,我也不会丢失任何数据。但是所有调用都是在同一个工作线程上进行的,我没有利用并行性。 :-/

【问题讨论】:

    标签: android multithreading kotlin coroutine


    【解决方案1】:

    TL;DR:使用协程时是否可能耗尽线程? 答案是否定的(死锁是另一个问题)。但是,是否有可能以某种方式使用协程,这意味着您的并发性受线程数的限制?是的。

    我认为您必须了解的第一件事是阻塞和非阻塞/暂停/异步函数之间的区别。

    真正的挂起/非阻塞/异步函数具有一些长时间运行的功能,但在长时间运行的任务完成之前适当地产生执行控制,这就是您真正利用协同程序获得的并发性的方式。让我演示一下。

    在 1 个线程上具有内部长时间运行挂起功能的多个协程

    val singleThread = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
    
    fun main() = runBlocking {
        val start = System.currentTimeMillis()
        val jobs = List(10) {
            launch (singleThread){
                delay(1000)
                print(".")
            }
        }
        jobs.forEach { it.join() }
        val end = System.currentTimeMillis()
        println()
        println(end-start)
    }
    

    这里我们有 10 个协程,它们在 1 个线程上快速连续启动。他们都使用挂起函数delay来模拟一个耗时1000毫秒的长时间运行的任务。但是……整个过程在 1018 毫秒内完成。对于熟悉纯线程并发的人来说,这会有点奇怪。解释来了。但为了绝对清楚,这里是相同的代码,但使用Thread.sleep 而不是delay

    1个线程上的多个协程具有内部长时间运行的阻塞功能

    fun main() = runBlocking {
        val start = System.currentTimeMillis()
        val jobs = List(10) {
            launch (singleThread){
                Thread.sleep(1000)
                print(".")
            }
        }
        jobs.forEach { it.join() }
        val end = System.currentTimeMillis()
        println()
        println(end-start)
    }
    

    同样的代码,但有一个阻塞 Thread.sleep 花了 10027 毫秒。每个协程阻塞了它所在的线程,因此,我们的 10 个协程实际上是串联执行的。在执行长时间运行的函数时,没有将控制权交还给调度程序。

    您可以阅读来自 Roman Elizarov here 的关于非阻塞挂起和阻塞调用之间区别的更详细说明

    在您的情况下,我怀疑您正在使用阻塞 IO 库来检索数据。这意味着这些调用中的每一个都会阻塞它所在的线程,并且在 IO 任务完成时不会将控制权交给调度程序。

    我的建议是:

    • 继续使用 Dispatchers.IO
    • 开始使用非阻塞库来检索您的数据。我推荐使用 CIO 引擎的 ktor http client

    但是当你同时做一些事情时你的数据丢失怎么办?

    这里没有足够的信息可以确定,但是,我认为您没有以考虑并发性的方式构建逻辑。在真正的并行执行中,第 3 次滑动可能会在第 2 次或第 1 次滑动完成之前完成。如果您的更新不是幂等的,或者您在每个更新请求中都传递了部分数据集,那么您可能会在其他更新之前处理更新 3,并在更新 1 和 2 最终到达时忽略它们。

    【讨论】:

    • 感谢您的解释! Dispatchers.IO 正在启动一系列方法并且并非所有方法都是suspend 的问题的一部分。我修好了。但真正的问题是该项目正在使用Semaphore 库,最后一个线程卡在synchronized 块中。我不确定为什么 Semaphore 被添加到这个项目中(在我之前),但在删除它之后,所有调用总是完成。再次困惑:D。
    • 我终于找到了根本原因——synchronized 块仅在写入而不是在删除值时用于共享 HashMap。
    • @Angelina 啊,是的,这显然会导致问题。就像一个指针 - 如果你要共享可变状态并且你想使用协程并发,可能值得将共享可变状态封装在一个actor中,并通过通道通信实现线程安全 -> kotlinlang.org/docs/reference/coroutines/…
    猜你喜欢
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 2010-09-27
    • 1970-01-01
    • 2018-03-06
    • 2020-05-21
    • 2017-08-18
    相关资源
    最近更新 更多