协程似乎并不比 JVM 线程消耗更少的资源

问题描述 投票:0回答:1

我做了一个基准测试(参考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的比例。

但是从上图中我没有看到协程和线程池之间的内存使用有任何明显的差异。

我设计的基准是错误的吗?

java multithreading kotlin kotlin-coroutines coroutine
1个回答
0
投票

协程(但不仅仅是协程)背后的主要思想是提供有些矛盾的属性:

  1. 始终尽可能利用所有 CPU(只要我们有事情要做)。
  2. 产生相对较少数量的线程,因为它们占用大量资源。
  3. 一次最多处理 CPU(或 CPU*2)CPU 密集型线程,否则线程会争夺 CPU 访问权。
  4. 保持代码简单。

您的示例没有提供 1. 您以非常低效的方式利用资源,因为您的应用程序几乎一直处于空闲状态,而它有很长的任务队列需要处理。

很久很久以前,我们会使用一种模型,每个任务启动一个线程 - 提供 1. 和 4.,但显然不满足 2. 和 3.

我们可以使用包含 50 个线程的线程池。然后,通过选择线程数,我们可以在满足 1. 和 2./3 之间进行平衡。较低的数字可能意味着所有线程都在等待,浪费 CPU。数字越大意味着线程需要更多内存,并且我们可能会同时处理太多任务,因此它们必须争夺对 CPU 的访问权。另外,每个任务都必须为其数据分配一些内存,因此,最佳情况下,我们应该处理尽可能少的任务,仅满足 1。使用线程池,我们无法完全控制这一点。我们只能尝试线程数量来获得最佳折衷方案,但这需要一些工作,而且仍然不是最佳的。

由于上述原因,我们开始使用异步模型:回调、Future、反应流等。它们提供了 1.、2. 和 3.,因此它们以非常高效的方式调度任务和使用资源,但它们对于4.

协程与其他异步模型的底层功能几乎相同,它们对于 1.、2. 和 3. 具有非常相似的属性,但它们允许保持代码像“很久很久以前”的情况一样简单 - 只需生成每个任务一个协程,仅此而已。我们从上到下编写代码,我们可以在CPU密集型、等待I/O或等待与其他任务同步之间平滑切换,我们可以轻松地将一个任务拆分为并发子任务,然后再次加入等等 - all同时仍然保持代码资源效率。

© www.soinside.com 2019 - 2024. All rights reserved.