【问题标题】:What is the Android coroutine equivalent to Executor service什么是相当于Executor服务的Android协程
【发布时间】:2020-12-12 08:27:05
【问题描述】:

我有一个ExecutorService 代码 sn-p,我正在尝试将其转换为协程以测试性能。然而,无论我如何构建协程代码,ExecutorService 都快得多。我认为协程应该可以提高性能

功能:

  • 在后台线程中运行
  • 执行 200000 次操作(计数器++)
  • 传递给 UI 线程的发布时间
  • 代码在 ViewModel 中运行,更新时间文本视图
  • 执行器服务代码在模拟器上大约需要 150 毫秒
  • 我编写的任何协程代码都需要更长的时间

与以下代码等效的协程是什么:

fun runCodeExecutorService() {
        spinner.value = true
        val executorService = Executors.newFixedThreadPool(NUMBER_OF_CORES * 2)
        val result = AtomicInteger()

        val startTime = System.currentTimeMillis()

        val handler: Handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(inputMessage: Message) {
                time.value = toTime(System.currentTimeMillis() - startTime)
                spinner.value = false
                Log.d("tag", "counter Executor = " + result.get())
            }
        }
        thread(start = true) {
            for (i in 1..NUMBER_OF_THREADS) {
                executorService.execute {
                    result.getAndIncrement()
                }
            }
            executorService.shutdown();
            executorService.awaitTermination(2, TimeUnit.MINUTES)
            val msg: Message = handler.obtainMessage()
            val bundle = Bundle()
            bundle.putInt("MSG_KEY", result.get())
            msg.data = bundle

            handler.sendMessage(msg)
        }
    }

其中 NUMBER_OF_CORES 是 val NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors() NUMBER_OF_THREADS 为 200000

【问题讨论】:

    标签: android kotlin kotlin-coroutines coroutine


    【解决方案1】:

    @George 实际上你的代码是阻塞的,实际的代码可能看起来像这样:

    fun runCodeCoroutines() = viewModelScope.launch {
            spinner.value = true
            val result = AtomicInteger()
    
            val startTime = System.currentTimeMillis()
    
            withContext(Dispatchers.Default) {
                result.aLotOfCoroutines()
            }
    
            // 3
            time.value = toTime(System.currentTimeMillis() - startTime)
            spinner.value = false
            Log.d("tag", "counter Dispatchers.Default = " + result.get())
    
        }
    
    suspend fun AtomicInteger.aLotOfCoroutines() {
    
            coroutineScope {
                repeat(NUMBER_OF_THREADS) {
                    launch(Dispatchers.Default) { // 1
                        getAndIncrement()
                    }
                }
            } // 2
    
        }
    

    aLotOfCoroutines 是你的代码 这大概是我得到的结果。

    基准: 协程代码 ~ 1.2 秒 执行程序代码 ~ 150 毫秒

    还有另一个版本的代码,我将线程数分成 200*1000

    suspend fun massiveRun(action: suspend () -> Unit) {
            coroutineScope { // scope for coroutines
                repeat(NUMBER_OF_TASKS) {
                    launch {
                        repeat(NUMBER_OF_ACTIONS) { action() }
                    }
                }
            }
        }
    

    大约需要 35 - 40 毫秒 但是,Executor 服务中的相同故障大约需要 25 - 35 毫秒

    更接近但总体上更好

    我的结论是,在性能方面,Executor Service 仍然比 Coroutines 更高效

    只有在语法和精确性能不重要时(即网络调用等),协程才会更好

    【讨论】:

    • 我将您的问题视为“如何在惯用的 Kotlin 协程中编写任务生成”(语义上等效)。事实证明,您正在编写基准测试。协程当然比Runnable 重。它可以在不阻塞线程的情况下暂停和恢复;它管理取消和超时。这是一个完全不同的苹果与橘子的比较。
    • 感谢您的澄清,我想我很清楚时间在我的问题中至关重要。协程被记录为轻量级线程,这个官方链接:developer.android.com/kotlin/coroutines-adv 建议协程提高性能(即改善当前情况,大多数应用程序都会有执行器机制),而我从基准测试中了解到它不一定会提高它,它只是更清洁,更容易使用,有没有一种方法,在性能方面,协程比 Executors 更好?
    • 它们比线程轻。如果您的网络调用阻塞了一个线程,您应该期望通过切换到回调来提高性能。由于回调很难管理,协程是处理它们的抽象。我认为这就是性能声明的来源。
    • Executor 可以用作协程调度器。这告诉你协程是比回调更高层次的抽象。您可以将协程恢复和暂停之间的代码视为Runnable,供执行程序运行。
    【解决方案2】:

    一个粗略的等价物是调度程序。

    suspend fun aLotOfCoroutines() {
        spinner.value = true
        val result = AtomicInteger()
    
        val startTime = System.currentTimeMillis()
    
        coroutineScope {
            repeat(NUMBER_OF_THREADS) {
                launch(Dispatchers.Default) { // 1
                    result.getAndIncrement()
                }
            }
        } // 2
    
        // 3
        time.value = toTime(System.currentTimeMillis() - startTime)
        spinner.value = false
        Log.d("tag", "counter Dispatchers.Default = " + result.get())
    }
    
    1. 我们可以将Dispatchers.Default 用于非阻塞任务,而不是创建和停止新的执行程序。

    2. 对于结构化并发,coroutineScope 在其所有子协程完成之前不会返回。这就是我们不需要显式等待完成的原因。

    3. 因为这个方法是在Dispatchers.Main中调用的,所以这些行也会在主线程中运行。


    如果真的要使用自定义线程池,可以在执行器上使用asCoroutineDispatcher扩展方法。


    在知道 OP 对性能数据感兴趣后,我做了更多调查。

    在这个“测试”中:

    1. 任务很便宜。因此,每个任务的任何开销都非常明显。
    2. 此任务正在处理一个竞争激烈的资源,以致于并行化速度更快。单线程运行它需要 1 毫秒。

    我认为公平地说这不像任何真正的工作量。

    不管怎样,下面的工作池和线程池实现的时间差不多。

    coroutineScope {
        val channel = produce(capacity = 64) {
            repeat(JOB_COUNT) { send(Unit) }
        }
    
        // The fewer workers we launch, the faster it runs
        repeat(Runtime.getRuntime().availableProcessors() * 2) {
            launch {
                for (task in channel) {
                    result.getAndIncrement()
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-08
      • 1970-01-01
      • 2014-11-01
      • 2019-12-05
      相关资源
      最近更新 更多