当前位置: 首页 > news >正文

Android开发:Kotlin协程并发模型(人话版)

一、核心基础:协程的并发与并行

1. 核心前提:并发 ≠ 并行

  • 并发:单核CPU场景下,任务快速切换(毫秒/微秒级),看起来像同时执行,本质是「轮流执行」(如单线程内的协程、单线程多任务)。

  • 并行:多核CPU场景下,多个任务在不同核心上物理同时执行,本质是「真正同时跑」。

2. 协程的并发与并行逻辑

  • 单线程内的协程:仅能实现「并发」,依赖协程主动让出CPU(如await、IO等待),切换速度比线程快100~1000倍(无内核切换开销)。

  • 协程的「并行」:无法单独实现,必须依托「多线程/多进程 + 多核CPU」—— 协程挂靠在操作系统线程上,多个线程分配到不同CPU核心,实现协程并行。

  • 关键结论:同一时刻,真正在CPU上运行的协程数量 ≤ 活跃的操作系统线程数量(协程必须挂靠线程执行,一个线程同一时刻只能执行一个协程)。

二、协程运行机制(结合实战代码)

1. 实战代码解析(IO并行请求)

suspendfunloadData():CombinedData=coroutineScope{valuserDeferred=async(Dispatchers.IO){api.getUser()}valnewsDeferred=async(Dispatchers.IO){api.getNews()}CombinedData(userDeferred.await(),newsDeferred.await())}

2. 逐步运行逻辑

  • 进入coroutineScope:创建父协程,父协程运行在调用它的线程(如安卓主线程)。

  • 启动async协程:两个async分别创建子协程,指定Dispatchers.IO,提交到IO线程池,线程池分配空闲线程(如Thread-IO1、Thread-IO2)执行网络请求;async不等待,直接返回Deferred对象,代码继续执行。

  • 执行await():父协程调用await()的瞬间,会「挂起」并「立刻释放当前占用的线程」(无需等待所有await或所有子协程完成)。

  • 协程恢复:子协程执行完成(网络请求结束),父协程重新获取线程,继续执行后续代码,组装并返回CombinedData。

3. 关键细节:await()与线程释放

  • 任意一个协程(父协程/子协程)调用await()(或任何挂起函数),调用者协程会立即挂起,同时释放当前占用的线程。

  • 线程释放与协程作用域(coroutineScope)无关:coroutineScope仅负责等待所有子协程完成,不管理线程释放;线程释放由「协程挂起」决定。

  • 恢复后线程的归属:若协程绑定Dispatchers.Main(主线程),恢复后必回主线程;若绑定Dispatchers.IO/Default(线程池),恢复后线程可能变化(随机分配线程池中的空闲线程)。

三、协程调度器详解(Dispatchers)

1. 两大核心调度器(Kotlin官方规则)

调度器适用场景最大线程数规则核心特点
Dispatchers.DefaultCPU密集型任务等于CPU核心数(如8核=8个,10核=10个)线程全程占用CPU,多开线程会增加切换开销,降低效率
Dispatchers.IOIO密集型任务max(64, CPU核心数)(绝大多数设备=64)线程99%时间在等待(如网络、文件读写),不占CPU,多开线程提升并发效率

2. 关键补充

  • CPU密集型任务:全程纯计算、不等待,如图片压缩、JSON解析、加密解密、复杂算法运算(必须用Default)。

  • IO密集型任务:主要时间在等待,如网络请求、文件读写、数据库操作(必须用IO)。

  • IO线程上限64的原因:64个线程足够支撑移动端极高并发,再多会浪费内存(线程栈占用)和增加内核切换开销,是行业通用最优值。

四、常见疑问答疑(高频踩坑点)

1. 疑问:启动100个async(IO调度器),线程池不够用会怎样?

解答:不会崩溃、不卡顿、不报错。IO线程池默认上限64,剩余36个协程会进入轻量级等待队列,等有空闲线程(IO线程完成等待、释放线程)后,自动取出执行;排队的协程无内存开销,不占用CPU。

2. 疑问:8核手机IO线程上限64,10核是不是80?

解答:不是。IO线程上限遵循max(64, CPU核心数),只要CPU核心数≤64,无论8核、10核、32核,上限都是64;只有CPU核心数>64(如服务器65核),上限才等于核心数。

3. 疑问:父协程中多个await,需要所有await都调用才释放线程吗?

解答:不需要。只要执行任意一个await(),调用它的协程就会立即挂起、释放线程;后续await()会再次挂起、释放线程,直到所有子协程完成,协程恢复。

4. 疑问:协程挂起(await)后,恢复时一定能回到原来的线程吗?

解答:不一定,看调度器:绑定Dispatchers.Main(主线程),恢复后必回主线程;绑定Dispatchers.IO/Default(线程池),恢复后可能切换到线程池中的其他空闲线程。

五、核心总结(必记)

  • 协程:用户态轻量级任务,依赖线程执行,无线程无法运行;核心优势是「用户态快速切换」和「挂起不阻塞线程」。

  • 并行:协程需依托多线程+多核CPU,同一时刻运行的协程数≤线程数;并发:单线程内协程快速切换,无需多核。

  • 调度器选择:CPU密集用Default(线程数=核心数),IO密集用IO(上限64),主线程操作绑定Main。

  • await():调用即挂起、释放线程,恢复线程由调度器决定;coroutineScope仅负责等待子协程,不管理线程。

http://www.jsqmd.com/news/637619/

相关文章:

  • 如何用Spleeter实现快速音频分离?3种模式完整指南
  • 2026年评价高的AI获客营销推荐榜单公司 - 行业平台推荐
  • 告别统计软件困境:虎贲等考 AI,让数据分析从 “硬核难题” 变 “轻松通关”
  • 手把手教你用STC8A8K64D4的4个串口同时打印4路ADC数据(附完整工程)
  • BiliTools哔哩哔哩工具箱2026年终极跨平台解决方案:5分钟快速上手完整指南
  • Sentaurus TCAD实战——TCL脚本自动化仿真流程设计
  • OpenPose Unity插件:5分钟实现实时多人姿态估计
  • Jackson配置全指南:从LocalDate序列化到自定义日期格式(附JSR310模块详解)
  • 一天一个开源项目(第72篇):everything-claude-code - 最系统化的 Claude Code 增强框架
  • 从LLM到可执行Agent:2026奇点大会指定框架的Tool Calling Pipeline全链路拆解,含4类超时熔断实战配置
  • Unity WebGL实战:用AVProVideo搞定海康监控M3U8流播放(附XChart数据可视化技巧)
  • 基于模型剪枝与量化的YOLOv5边缘计算加速:从训练到部署完整实战
  • ConvNeXt 系列改进:ConvNeXt 添加 MetaFormer 风格池化层,简化 Block 并保持性能
  • 递归、搜索与回溯算法(专题六:记忆化搜索)
  • Keil RTX5在STM32F103上的实战移植指南:从零开始到LED闪烁
  • Phi-3-mini-4k-instruct-gguf:基于Proteus的单片机仿真项目智能分析与代码生成
  • 激活函数:神经网络的「非线性灵魂」,让模型从“直线”走向“万能”
  • 怎样排查Laravel中Scout全文搜索导致的数据同步报错_队列与底层状态
  • 从SDR#到MATLAB:用RTL-SDR玩转无线信号分析,一份完整的软硬件环境搭建清单
  • GD32F303RCT6硬件SPI配置MT6701磁编码器的保姆级教程(附SPI分频计算与避坑点)
  • 从“不占上下文”的误区,看 Harness 架构的隐形陷阱
  • 如何用 Basic Pitch 实现精准音频转 MIDI?Spotify 实验室的开源黑科技全解析
  • FPGA做超声波测距,如何用BCD码优化避免除法?一个资源节省技巧分享
  • arm64麒麟服务器内网离线安装minio
  • Tonic:构建 RAG Harness 的合成数据工具
  • [具身智能-364]:LeRobot 不是通用机器人控制系统(如 ROS2 导航/规划栈),而是专注于“感知-决策-动作”端到端学习的 AI 框架。他们共同成为具身智能时代最重要的开源基础设施之一
  • Jitsi Meet与GitLab CI/CD集成:实现视频会议平台的自动化测试与部署全流程
  • 别再用笨办法了!用Keil uVision5给STM32F103C8T6点灯,这份保姆级教程带你避开所有新手坑
  • Vicinae开发者API参考手册:构建高效搜索界面的完整指南
  • 从链表到二叉树:树形结构的入门与核心性质解析