Android和Kotlin协程:线程用完了吗?

时间:2019-04-13 15:44:46

标签: android multithreading kotlin coroutine

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

1 个答案:

答案 0 :(得分:1)

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

我认为您必须首先了解的是阻止功能和非阻止/暂停/异步功能之间的区别。

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

在一个线程上具有内部长期运行挂起功能的多个协程

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
  • 开始使用非阻塞库检索数据。我建议将ktor http client与CIO引擎一起使用。

但是同时执行操作会丢失数据吗?

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