2.1-结构化并发:[一个协程] 到底指的是什么

在协程里面有一个比较重要的知识点:结构化并发。处理结构化并发和异常时需要清楚协程之间的关系,比如父子协程,还有不同 CoroutineScope 的协程之间的关系。但在此之前,我们需要知道 [一个协程] 到底指的是什么。

在讲 [一个协程] 是指什么之前,我们先来看下 [一个线程] 指的是什么。

[一个线程] 指的是什么?

val thread = thread {
	// ...
}

上面的代码很简单,我们创建了一个线程,这里的 [一个线程] 就是指的 Thread 对象,我们使用线程的步骤是:创建一个线程对象,然后用这个线程对象启动线程。

Thread 对象实现了对于 [线程] 这个抽象概念的管理,线程指的是 [在程序里单独运行的一条业务线],我们通过 Thread 对象可以完成对这个 [单独运行的业务线] 的管理

比如可以通过 Thread 对象获取元数据、停止线程等等:

val thread = thread {} // 启动线程
thread.name // 获取线程名称
thread.interrupt() // 停止线程

Thread 对象承载了线程的管理功能,所以在这里把 Thread 对象看成 [一个线程] 就是理所当然的

[一个协程] 指的是什么?

val scope = CoroutineScope(EmptyCoroutineContext)
val job = scope.launch {}

在上面的例子中,我们创建了一个 CoroutineScope 对象,然后调用 scope.launch 就启动了一个协程。这里的 [一个协程] 具体是 launch 的返回值 Job?还是 scope?

这里先透露下 [一个协程] 具体所指:

  • 从管理协程流程的视角分析,把 Job 对象看成是一个 [协程对象],但是它只包含了跟协程的流程相关的功能

  • 从父子协程的视角分析,launch 返回的 Job 或 launch 内的 CoroutineScope 都是属于 [协程对象];外层 CoroutineScope 是一个 [最顶级] 的管理器,CoroutineScope 和 launch 返回的 Job 是从属关系(例如 scope.cancel 可以取消由它调用 launch 所启动的所有协程,包括 launch 返回的 Job;job.cancel 只能取消自身及其子协程)

Job:管理协程流程的 [一个协程]

启动协程是通过 launch,我们就先从 launch 的源码分析:

Builders.common.kt

public fun CoroutineScope.launch(
	context: CoroutineContext = EmptyCoroutineContext,
	start: CoroutineStart = CoroutineStart.DEFAULT,
	block: suspend CoroutineScope.() -> Unit
): Job {
	val newContext = newCoroutineContext(context)
	// 创建 LazyStandaloneCoroutine 或 StandaloneCoroutine
	// 但最终函数返回的类型是 Job
	val coroutine = if (start.isLazy)
		LazyStandaloneCoroutine(newContext, block) else
		StandaloneCoroutine(newContext, activity = true)
	coroutine.start(start, coroutine, block)
	return coroutine
}

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

private class LazyStandaloneCoroutine(
    parentContext: CoroutineContext,
    block: suspend CoroutineScope.() -> Unit
) : StandaloneCoroutine(parentContext, active = false) {
    private val continuation = block.createCoroutineUnintercepted(this, this)

    override fun onStart() {
        continuation.startCoroutineCancellable(this)
    }
}

AbstractCoroutine.kt

// 实现了 Job 和 CoroutineScope
@InternalCoroutinesApi
public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
	...
}

launch 函数会创建 LazyStandaloneCoroutine 或 StandaloneCoroutine,LazyStandaloneCoroutine 也是 StandaloneCoroutine 的子类,StandaloneCoroutine 继承自 AbstractCoroutine,AbstractCoroutine 实现了 Job 和 CoroutineScope。launch 函数最终需要返回的是 Job 类型的对象。

这里就会有疑问:为啥不直接返回 StandaloneCoroutine 而是返回 Job 呢?

实际上协程为了做到责任的拆分,在使用上通过返回 Job 对象,限制我们只能用 Job 的 API 管理协程的流程

可以使用 Job 对象操作管理协程流程:

// job 提供管理协程流程的 API
job.start() // 启动协程
job.cancel() // 取消协程
job.isActive // 协程是否活跃
job.isCancelled // 协程是否取消
job.isCompleted // 协程是否完成,协程被取消也为 true
job.parent // 该协程的父协程
job.children // 该协程的子协程
job.cancelChildren() // 取消所有该协程的子协程

从管理协程流程的视角分析,我们可以把 Job 对象看成是一个 [协程对象],但是它只包含了跟协程的流程相关的功能

CoroutineScope:总管协程所有功能和数据的 [一个协程]

上一节提到 Job 是用来管理协程流程的,通过 launch 函数可以知道它甚至是能启动已经创建好的协程,为什么还要用 CoroutineScope 启动协程呢?

实际上 CoroutineScope 是一个 [最顶级] 的管理器,协程的所有数据和功能都能从 CoroutineScope 里面找到

比如可以通过 CoroutineScope 获取在哪个线程执行,也就是 ContinuationInterceptor:

fun main() = runBlocking<Unit> {
	val scope = CoroutineScope(Dispatchers.IO)
	val job = scope.launch {
		// outer 和 inner 获取的 ContinuationInterceptor 是同一个对象
		val outer = scope.coroutineContext[ContinuationInterceptor] 
		val inner = this.coroutineContext[ContinuationInterceptor]
		println("outer: $outer")
		println("inner: $inner")
	}
}

输出结果:
outer:Dispatchers.IO
inner:Dispatchers.IO 

如果在 launch 指定了和 scope 不同的 ContinuationInterceptor,launch 内的 CoroutineScope 获取的 ContinuationInterceptor 和外面的也会是不同的:

fun main() = runBlocking<Unit> {
	val scope = CoroutineScope(Dispatchers.IO)
	val job = scope.launch(Dispathcers.Default) {
		// launch 指定了 ContinuationInterceptor
		// outer 和 inner 获取的 ContinuationInterceptor 不是同一个对象
		val outer = scope.coroutineContext[ContinuationInterceptor] 
		val inner = this.coroutineContext[ContinuationInterceptor]
		println("outer: $outer")
		println("inner: $inner")
	}
}

输出结果:
outer:Dispatchers.IO
inner:Dispatchers.Default

scope.launch 返回的 Job 对象和 launch 内的 CoroutineScope 获取的 Job 是同一个对象,也是同一个 CoroutineScope 对象:

fun main() = runBlocking<Unit> {
	val scope = CoroutineScope(Dispatchers.IO)
	var innerJob: Job? = null
	var innerScope: CoroutineScope? = null
	val outerJob = scope.launch(Dispathcers.Default) {
		// this 是 StandaloneCoroutine,即实现了 Job 也实现了 CoroutineScope
		// outerJob 和 innerJob 是同一个对象
		// outerJob 和 innerScope 也是同一个对象
		innerJob = this.coroutineContext[Job] 
		innerScope = this
	}
	println("outerJob === innerJob: ${outerJob === innerJob}")
	println("outerJob === innerScope: ${outerJob === innerScope}")
}

输出结果:
outerJob === innerJob: true
outerJob === innerScope: true

CoroutineScope 和 Job 并不是并列关系而是从属关系,CoroutineScope 是所有的大总管,Job 只负责其中协程流程相关的功能

从父子协程的角度看,将 CoroutineScope 看成 [一个协程] 也是可以的,即可以把 launch 函数返回的 Job 看作是一个协程对象,也可以把 launch 内的 CoroutineScope 看成是一个协程对象

线程与协程的区别

传统的 Java 线程是把所有功能都放进了 Thread,所以它没有协程这种职责拆分的概念,Thread 对象就是线程对象。

协程把功能拆开,它的完整功能是由 CoroutineScope 管理,同时它把最核心的流程相关的功能拆到了 Job 。所以在协程里我们把 Job 和 CoroutineScope 看作是协程对象都是可以的

这是线程和协程的不同。

简单总结就是:线程所有功能都放到 Thread;协程做了更明确的职责化拆分,CoroutineScope 具备完整功能的管理,Job 负责协程流程的管理

在平常开发中更直观的视角看协程是,launch 的大括号就是一个协程,大括号内调用 launch 启动的协程就是子协程,以此类推。

fun main() = runBlocking<Unit> {
	val scope = CoroutineScope(Dispatchers.IO)
	// 直观的角度:一个大括号就是一个协程,大括号内的大括号就是子协程
	scope.launch(Dispathcers.Default) {
		launch {
		}
	}
}

总结

  • [一个线程] 指的就是 Thread 对象:Thread 对象实现了对于 [线程] 这个抽象概念的管理,线程指的是 [在程序里单独运行的一条业务线],我们通过 Thread 对象可以完成对这个 [单独运行的业务线] 的管理,Thread 对象承载了线程的管理功能

  • [一个协程] 从不同的视角分析 Job 和 CoroutineScope 都可以是协程对象:从管理协程流程的视角分析,把 Job 对象看成是一个 [协程对象],但是它只包含了跟协程的流程相关的功能;从父子协程的视角分析,launch 返回的 Job 或 launch 内的 CoroutineScope 都是属于 [协程对象];外层 CoroutineScope 是一个 [最顶级] 的管理器,CoroutineScope 和 launch 返回的 Job 是从属关系

  • 线程与协程的区别是:线程所有功能都放到 Thread;协程做了更明确的职责化拆分,CoroutineScope 具备完整功能的管理,Job 负责协程流程的管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值