结构化并发

449 阅读2分钟

什么是结构化并发,简单来说就是带有结构和层级的并发,这里的结构和层次指的就是协程的父子关系,而并发操作指的就是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之间的关系:

a6fb8e609ef748.png

而这个父子协程的关系就可以构成线程所不具备的结构化并发操作。

在前面代码中parentJob.join()会将代码挂起大约5s,而这个5s就是job3执行的时间,所以这就说明只有当其子协程都执行完毕后,parentJob才执行完成。

还有一个非常重要的结构化操作就是结构化取消。这个在Android开发中至关重要,比如我们使用MVVM架构,我们的逻辑都写在ViewModel中,而ViewModel会在页面销毁后销毁,这时我们使用ViewModel的协程范围即ViewModelScope来启动协程,在协程中做一些操作,不管这个操作调用得多复杂,当调用ViewModelScope.cancel()时把最外面的父协程取消掉,里面所有的子协程业务都会取消,会减少内存泄漏的风险。

总结:

  • 对于一个协程来说,只有它所有子协程都完成后,它才算是完成状态。

    这种关系是线程中不存在的,比如我主线程开启子线程A,子线程A中再开启子线程B,这时子线程A运行完就结束了,不会和A和C有任何关系。

  • 当我们执行父协程的cancel()方法时,其子协程也都会取消。