理解kotlin limitedParallelism(1)与Actor模型

Dispatcher.kt

复制代码
package com.example.demo.process

import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.incrementAndFetch

object Dispatcher {
    private val logger = KotlinLogging.logger {}

    //获取当前机器的 CPU 核心数,后续所有线程池大小的计算都基于此
    private val cpuCore = Runtime.getRuntime().availableProcessors()

    //使用 AtomicInt 作为线程编号的原子计数器,保证线程命名的唯一性和线程安全。
    @OptIn(ExperimentalAtomicApi::class)
    private val newThreadId: AtomicInt = AtomicInt(0)

    @OptIn(ExperimentalAtomicApi::class)
    private fun createDispatcherThreadPool(name: String, minSize: Int, maxSize: Int): CoroutineDispatcher {
        //核心线程数(下限 ≥1)
        val corePoolSize = minSize.coerceAtLeast(1)

        //最大线程数(范围:corePoolSize ~ 128)
        val maximumPoolSize = maxSize.coerceIn(corePoolSize, 128)

        logger.info { "create dispatcher $name, corePoolSize=$corePoolSize, maximumPoolSize=$maximumPoolSize" }

        return ThreadPoolExecutor(
            corePoolSize,                       //核心线程数,源码:workerCountOf(c) < corePoolSize就会创建线程,所以就算是是:任务一个执行完了来,
            maximumPoolSize,                    // TODO 此参数其实压根不会生效,因为无界队列
            30,                   //回收非核心线程。 源码中: workQueue.poll(30, SECONDS)  ← 限时等待 30 秒, 如果线程没有任务,就会被回收(非核心线程),核心线程不会回收!!!
            TimeUnit.SECONDS,
            LinkedBlockingQueue(),  // 无界队列
        ) { runnable ->
            /*
            线程命名 :格式为 {name}-{自增ID} ,例如 Scheduler-1 、 Actor-3 ,便于 jstack 排查。
            且是守护线程
             */
            Thread(runnable, "$name-${newThreadId.incrementAndFetch()}").apply { isDaemon = true }
        }.asCoroutineDispatcher() //是 Kotlin 协程库( kotlinx.coroutines )提供的一个扩展函数,定义在 java.util.concurrent.Executor 上,用于把 Java 线程池(Executor) 桥接到 Kotlin 协程的调度器体系 中
    }

    // 轻量级调度器,固定 2 个核心线程,适合处理定时任务、延迟任务等轻量操作。
    val Scheduler = createDispatcherThreadPool("Scheduler", 2, cpuCore / 2)

    // 主力执行调度器,至少有 10 个核心线程,承载进程内大部分的业务逻辑执行
    //Dispatcher.Actor.limitedParallelism(1)的意思是:复用这个Actor调度器的底层线程池,然后再创建一个子调度器,并发度是1,这样子
    val Actor = createDispatcherThreadPool("Actor", cpuCore.coerceAtLeast(10), cpuCore * 2)
}

main.kt

复制代码
package com.example.demo

import com.example.demo.process.Dispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.springframework.boot.autoconfigure.SpringBootApplication
import java.util.concurrent.TimeUnit

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
//    runApplication<DemoApplication>(*args)

    val actorScope = CoroutineScope(CoroutineName("DemoApplication") + Dispatcher.Actor)
    val schedulerScope = CoroutineScope(Dispatcher.Scheduler)


    //------------------------------------test1-------------------------------------
    var count = 0

    actorScope.launch(CoroutineName("TIMER")) {
        for (i in 1..10000) {
            //上下文调度器本来就是多个协程可以启动
            launch {
                count++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println(">>>>>count:${count}")

    //------------------------------------test2-------------------------------------

    var limitedParallelismCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 默认使用父协程上下文(也就是并发度也是1的调度器,起多个协程业没用)
            launch {
                limitedParallelismCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("-----limitedParallelismCounter:${limitedParallelismCounter}")

    //------------------------------------test3-------------------------------------

    var limitedParallelismAnotherCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 换了上下文(也就是又变成多线程了)
            schedulerScope.launch {
                limitedParallelismAnotherCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("=====limitedParallelismAnotherCounter:${limitedParallelismAnotherCounter}")


    //------------------------------------test4-------------------------------------

    var limitedParallelismSameCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 其实协程作用阈还是变了
            actorScope.launch {
                limitedParallelismSameCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("*****limitedParallelismAnotherCounter:${limitedParallelismSameCounter}")
}

/*
15:22:27.868 [main] INFO com.example.demo.process.Dispatcher -- create dispatcher Scheduler, corePoolSize=2, maximumPoolSize=8
15:22:27.873 [main] INFO com.example.demo.process.Dispatcher -- create dispatcher Actor, corePoolSize=16, maximumPoolSize=32
>>>>>count:9974
-----limitedParallelismCounter:10000
=====limitedParallelismAnotherCounter:9969
*****limitedParallelismAnotherCounter:9962
 */

总结:

复制代码
actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) 

通过传入协程上下文,限制该协程下最多只有一个协程在运行,这样子实现Actor模型

相关推荐
微学AI4 小时前
一根针指向所有方向:挂谷猜想对 LLM Agent 技能-记忆架构的启示
开发语言·人工智能·架构·挂谷猜想
summerkissyou19874 小时前
Android - 摄像头 - hal - 开发教程,例子,常见问题,分析方法,解决方案
android
豆瓣鸡5 小时前
算法日记 - Day3
java·开发语言·算法
summerkissyou19875 小时前
Android 16 架构图
android
韭菜炒鸡肝天5 小时前
VTK开发笔记(一):VTK介绍,Qt..+VSx+VTK.编译
开发语言·笔记·qt
神龙天舞20016 小时前
MySQL 备库为什么会延迟好几个小时
android·数据库·mysql
Aaron - Wistron6 小时前
Web API C# (Furion版)带 单元测试
开发语言·后端·c#
Dxy12393102167 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
0566468 小时前
Python康复训练——常用标准库
开发语言·python·学习
骊城英雄8 小时前
基于C#+avalonia ui实现的跨平台点胶机灌胶监控控制上位机软件
开发语言·ui·c#