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 任务完成时不会将控制权交给调度程序。
我的建议是:
但是当你同时做一些事情时你的数据丢失怎么办?
这里没有足够的信息可以确定,但是,我认为您没有以考虑并发性的方式构建逻辑。在真正的并行执行中,第 3 次滑动可能会在第 2 次或第 1 次滑动完成之前完成。如果您的更新不是幂等的,或者您在每个更新请求中都传递了部分数据集,那么您可能会在其他更新之前处理更新 3,并在更新 1 和 2 最终到达时忽略它们。