Kotlin 协程设计思想(六):结构化并发到底是什么?为什么 Google 一直强调 Scope?
2026/6/6 17:51:53 网站建设 项目流程

—— 从 GlobalScope、CoroutineScope、LifecycleScope 到 ViewModelScope,彻底讲透 Kotlin 协程最核心的设计哲学

前面几篇我们已经讲了:

CoroutineContext ↓ Job ↓ Dispatcher ↓ launch / async ↓ Exception

学到这里,很多同学已经能够熟练使用协程。

但是:

真正的协程核心 其实还没出现。

这个核心就是:

Structured Concurrency 结构化并发

如果说:

CoroutineContext 是协程运行环境 Job 是协程生命周期管理器 Dispatcher 是协程调度器

那么:

Structured Concurrency 就是整个协程体系的灵魂。

甚至可以说:

没有结构化并发 就没有 Kotlin 协程。

一、什么叫结构化并发?

先不要看官方定义。直接看现实生活。


假设公司里有个项目经理:

项目经理 │ ├── 开发A │ ├── 开发B │ └── 开发C

项目经理离职:

A继续上班? B继续上班? C继续上班?

显然不合理。


正常情况:

项目经理离开 ↓ 整个项目结束

这就是:

结构化

而协程也是一样。

例如:

viewModelScope.launch { launch { } launch { } launch { } }

结构:

ViewModelScope │ └── Parent Job │ ├── Child1 │ ├── Child2 │ └── Child3

这棵树:

有父亲 有孩子 有归属

这就是:

结构化并发

二、为什么 Google 要发明结构化并发?

先看 Java 时代。

例如:

new Thread(() -> { }).start();

随便开线程。


问题:

线程是谁创建的? 什么时候结束? 谁负责回收?

不知道。


于是:

线程越来越多 生命周期越来越乱 内存泄漏越来越严重

Kotlin 团队发现:

协程不能重蹈覆辙

于是提出:

所有协程必须有归属

即:

Scope

三、Scope 到底是什么?

很多人学协程:

CoroutineScope

一直背:

协程作用域

然后结束。


实际上:

Scope = Job树的根节点

例如:

val scope = CoroutineScope( SupervisorJob() + Dispatchers.Main )

结构:

CoroutineScope │ └── SupervisorJob

后面所有:

scope.launch { }

都会挂到这棵树下面。


四、为什么 GlobalScope 被疯狂吐槽?

很多人刚学协程:

GlobalScope.launch { }

觉得很方便。


但问题来了。

结构:

GlobalScope │ └── launch

没有真正父节点。


例如:

Activity关闭

协程:

继续运行

例如:

GlobalScope.launch { delay(10000) updateUI() }

10秒后:

Activity早没了

直接:

崩溃

或者:

内存泄漏

所以:

Google一直不推荐GlobalScope

原因就在这里。


五、为什么 ViewModelScope 这么好用?

例如:

viewModelScope.launch { }

内部:

SupervisorJob() + Dispatchers.Main

结构:

ViewModel │ └── ViewModelScope │ ├── 协程A │ ├── 协程B │ └── 协程C

当:

onCleared()

执行。


内部:

scope.cancel()

结果:

整个Job树取消

所有协程:

自动结束

这就是:

生命周期感知

的本质。


六、LifecycleScope 又是什么?

例如:

lifecycleScope.launch { }

结构:

Activity │ └── LifecycleScope

当:

onDestroy()

执行。


内部:

取消整个Scope

于是:

所有子协程结束

和:

ViewModelScope

本质一样。


七、为什么 repeatOnLifecycle 这么安全?

你最近正好在用:

repeatOnLifecycle( Lifecycle.State.STARTED )

很多人只会背:

推荐使用

不知道为什么。


实际上:

repeatOnLifecycle { }

每次:

STARTED

创建:

新的子Job

每次:

STOPPED

取消:

这个子Job

结构:

LifecycleScope │ └── Repeat Job │ ├── collectA │ ├── collectB │ └── collectC

所以:

页面不可见 ↓ 自动取消收集

页面回来:

重新创建Job

这就是:

Flow生命周期安全

的根源。


八、之前为什么要把5个 collect 合并?

前面优化过:

lifecycleScope.launch { repeatOnLifecycle( Lifecycle.State.STARTED ) { launch { collectA() } launch { collectB() } launch { collectC() } launch { collectD() } launch { collectE() } } }

当时的理解:

代码更整洁

其实更重要的是:

形成结构化并发

结构:

LifecycleScope │ └── Repeat Job │ ├── A ├── B ├── C ├── D └── E

生命周期结束:

整棵树取消

不用:

一个一个取消

九、为什么说 Scope 才是协程世界的边界?

其实:

CoroutineContext 解决运行环境 Job 解决生命周期 Dispatcher 解决调度

而:

Scope 解决归属问题

例如:

这个协程属于谁?

答案:

属于Scope

例如:

页面关闭怎么办?

答案:

取消Scope

例如:

ViewModel销毁怎么办?

答案:

取消Scope

所以:

Scope 才是协程生命周期边界。

十、终于理解 Google 的设计哲学

现在回头看:

Java线程

特点:

想开就开

而:

Kotlin协程

特点:

必须有Scope

因为:

Google不希望出现孤儿协程

希望:

每个协程 都有归属

这就是:

Structured Concurrency

的核心思想。


十一、最终总结

如果让我一句话解释:

结构化并发

我会这样说:

所有协程都必须挂在一棵Job树上。

如果让我解释:

Scope

我会说:

Scope就是这棵树的根节点。

如果让我解释:

GlobalScope

我会说:

没有归属的野孩子。

如果让我解释:

ViewModelScope

我会说:

跟随ViewModel生命周期的Job树。

如果让我解释:

LifecycleScope

我会说:

跟随Activity/Fragment生命周期的Job树。

真正理解结构化并发以后,会发现:

协程最牛逼的地方 从来不是轻量级线程。

而是:

生命周期可控。

下篇预告

到这里:

CoroutineContext ✓ Job ✓ Dispatcher ✓ launch / async / withContext ✓ Exception ✓ Structured Concurrency ✓

整个协程骨架已经出来了。

那么最后一个经常让人迷糊的问题来了:

SupervisorJob supervisorScope 到底有什么区别? 为什么一个是Context里的配置项, 一个是挂起函数? 它们到底应该怎么选?

下一篇我们继续:

《Kotlin 协程设计思想(七):SupervisorJob 与 supervisorScope 到底有什么区别?》

从 Job 树、异常传播到作用域隔离,彻底讲透 Kotlin 协程中的“异常隔离设计”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询