什么是结构化并发,简单来说就是带有结构和层级的并发,这里的结构和层次指的就是协程的父子关系,而并发操作指的就是cancel等操作。
一、协程父子关系
在Java中我们知道想并发编程就使用多线程,但是线程和线程之间却是没有父子关系的,而这些协程却是可以有父子关系的。
说起来可能不好理解,我们直接看个例子:
fun main() = runBlocking {
val parentJob: Job
var job1: Job? = null
var job2: Job? = null
var job3: Job? = null
//父协程
parentJob = launch {
//3个子协程
job1 = launch {
delay(1000L)
}
job2 = launch {
delay(3000L)
}
job3 = launch {
delay(5000L)
}
}
delay(500L)
//遍历父协程的`Job`中的子协程集和
parentJob.children.forEachIndexed { index, job ->
when (index) {
0 -> println("job1 === job is ${job1 === job}")
1 -> println("job2 === job is ${job2 === job}")
2 -> println("job3 === job is ${job3 === job}")
}
}
//等待父协程执行完成
parentJob.join()
logX("Process end!")
}
上面代码不难理解,直接看注释,打印结果全是true,这里也就表明parentJob中的children集和中的job和在其协程代码块中创建的job是同一个。
协程通过这种关系,来建立parentJob是其中3个子job的父协程,即协程和协程之间是有父子关系的。
二、结构化
我们来看一下源码,在Job中有如下代码:
public val children: Sequence<Job>
public fun attachChild(child: ChildJob): ChildHandle
可以看到每个Job对象都会有一个children属性,它的类型是Sequence,是一个惰性集合,那么我们就可以用一个简单的图来说明一下上面4个Job之间的关系:
而这个父子协程的关系就可以构成线程所不具备的结构化并发操作。
在前面代码中parentJob.join()会将代码挂起大约5s,而这个5s就是job3执行的时间,所以这就说明只有当其子协程都执行完毕后,parentJob才执行完成。
还有一个非常重要的结构化操作就是结构化取消。这个在Android开发中至关重要,比如我们使用MVVM架构,我们的逻辑都写在ViewModel中,而ViewModel会在页面销毁后销毁,这时我们使用ViewModel的协程范围即ViewModelScope来启动协程,在协程中做一些操作,不管这个操作调用得多复杂,当调用ViewModelScope.cancel()时把最外面的父协程取消掉,里面所有的子协程业务都会取消,会减少内存泄漏的风险。
总结:
-
对于一个协程来说,只有它所有子协程都完成后,它才算是完成状态。
这种关系是线程中不存在的,比如我主线程开启子线程A,子线程A中再开启子线程B,这时子线程A运行完就结束了,不会和A和C有任何关系。
-
当我们执行父协程的
cancel()方法时,其子协程也都会取消。