我做了一个基准测试(参考answer)来测试coroutiens和线程池中线程之间的内存使用情况:
val COUNT = 4_000
val executor: Executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())// 12 cores on my machine
fun main(array: Array<String>) = runBlocking{
val latch = CountDownLatch(COUNT)
val start = System.currentTimeMillis()
repeat(COUNT) {
launch(Dispatchers.Default) {
testByCoroutine(latch)
}
}
latch.await()
println("total: " + (System.currentTimeMillis() - start))
// testByThreadPool()
}
fun testByThreadPool() {
val latch = CountDownLatch(COUNT)
val start = System.currentTimeMillis()
for (i in 0..<COUNT) {
executor.execute {
val num: Int = request1()
println(request2(num))
latch.countDown()
}
}
try {
latch.await()
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
println("total: " + (System.currentTimeMillis() - start))
exitProcess(0)
}
fun testByCoroutine(latch: CountDownLatch) {
val num = request1()
val res: Int = request2(num)
println(res)
latch.countDown()
}
fun request1(): Int {
return doGet("https://bing.com")
}
fun request2(token: Int): Int {
val response: Int = doGet("https://bing.com")
return response + token
}
fun doGet(link: String): Int {
try {
val url = URL(link)
val conn = url.openConnection() as HttpURLConnection
conn.setRequestMethod("GET")
conn.setConnectTimeout(3000)
return conn.getResponseCode()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return -1
}
线程池案例:
total: 518218
total: 561510
Dispatchers.IO 案例:
total: 262719
根据答案:
Coroutines are not designed to be faster than threads, it is for lower RAM consumption
有一个benchmark说线程和协程之间消耗的内存似乎有相对恒定的6:1的比例。
但是从上图中我没有看到协程和线程池之间的内存使用有任何明显的差异。
我设计的基准是错误的吗?
协程(但不仅仅是协程)背后的主要思想是提供有些矛盾的属性:
您的示例没有提供 1. 您以非常低效的方式利用资源,因为您的应用程序几乎一直处于空闲状态,而它有很长的任务队列需要处理。
很久很久以前,我们会使用一种模型,每个任务启动一个线程 - 提供 1. 和 4.,但显然不满足 2. 和 3.
我们可以使用包含 50 个线程的线程池。然后,通过选择线程数,我们可以在满足 1. 和 2./3 之间进行平衡。较低的数字可能意味着所有线程都在等待,浪费 CPU。数字越大意味着线程需要更多内存,并且我们可能会同时处理太多任务,因此它们必须争夺对 CPU 的访问权。另外,每个任务都必须为其数据分配一些内存,因此,最佳情况下,我们应该处理尽可能少的任务,仅满足 1。使用线程池,我们无法完全控制这一点。我们只能尝试线程数量来获得最佳折衷方案,但这需要一些工作,而且仍然不是最佳的。
由于上述原因,我们开始使用异步模型:回调、Future、反应流等。它们提供了 1.、2. 和 3.,因此它们以非常高效的方式调度任务和使用资源,但它们对于4.
协程与其他异步模型的底层功能几乎相同,它们对于 1.、2. 和 3. 具有非常相似的属性,但它们允许保持代码像“很久很久以前”的情况一样简单 - 只需生成每个任务一个协程,仅此而已。我们从上到下编写代码,我们可以在CPU密集型、等待I/O或等待与其他任务同步之间平滑切换,我们可以轻松地将一个任务拆分为并发子任务,然后再次加入等等 - all同时仍然保持代码资源效率。