Skip to content

Files

Latest commit

7cab6cb · Feb 27, 2019

History

History
146 lines (89 loc) · 5.64 KB

arch_design_zh.md

File metadata and controls

146 lines (89 loc) · 5.64 KB

coobjc

framework_arch.png

  • 底层是协程内核,包括堆栈切换管理,协程调度程序的实现,协程之间通信通道的实现等。
  • 中间层是基于协同程序的运算符的包装器。 目前,它支持编程模型,如async / await,generator和Actor。
  • 顶层是系统库的协程扩展,目前涵盖了Foundation和UIKit的所有IO和耗时的方法。

coobjc 的架构设计分为以下几块:

  • Context切换
  • Coroutine的设计
  • Scheduler调度器
  • Channel设计
  • Api设计
  • Cancellation设计
  • Swift设计

Context 切换

因为iOS上ucontext是不能使用的,所以我们自己用汇编写了自己的 Context切换。主要包含2个操作 getcontextsetcontext, 分别对 arm64/armv7/x86_64/i386 四个架构做了实现。

下图简单描述了协程如何通过 getcontextsetcontext 实现了 yieldresume的操作。

context.png

Coroutine的设计

  • 可暂停恢复

通过 Context切换实现协程的暂停和恢复操作,对应 yieldresume 操作。

  • 自建调用栈

自建调用栈,通过单独申请一片内存作为协程执行的栈内存,然后通过coroutine_makecontext 构造成协程单独的调用栈内容,再通过 setcontext 进入协程。

  • 四种状态

Coroutine 有四种状态,READY/RUNNING/SUSPEND/DEAD, 其中RUNNING和 SUSPEND状态可能多次切换。

当进入 DEAD状态,我们会自动释放 Coroutine。

  • 可设置userdata
void *userdata;                     // Userdata.
coroutine_func userdata_dispose;    // Userdata's dispose action.

允许上层传入一个userdata,可以用来记录一些额外的信息。在上层Api中就是把 Coroutine的 ObjC 对象传到这里存储,实现了对象生命周期的一致性。

Scheduler调度器

有了协程,但是协程怎么才能够在我们iOS应用里面用起来呢?怎么和现有的代码进行交互呢? 协程具体是在哪个线程调度呢? 于是我们设计了协程调度器 Scheduler.

Scheduler负责调度所有的用户协程, 它内部管理一个协程队列, 然后不断循环从队列里面拿出协程进行执行, 当队列中没有协程的时候, 就切回线程执行。

基于这个设计,所以当我们要执行一个协程的时候, 只把协程加入线程的Scheduler的队列中, 然后是 Scheduler 负责执行它。

Scheduler本身也是一个协程。

scheduler调度逻辑如下:

scheduler.png

Channel 的设计

Channel 是 CSP(Communicating Sequential Processes) 并发模型中的 Process/Channel 的实现。 coobjc中的 Channel实现完全参考了 libtask 的实现。

�Channel 是协程之间传递数据的通道,中文一般我们叫管道。 Channel的特性是它可以阻塞式发送或接收数据(协程中的阻塞都不是真阻塞,只是yield让出cpu了)。

Channel分为无buffer有buffer无限buffer(可自动扩容的有buffer) 模式,下图描述了有无buffer的区别:

channel1.png

Channel的主要特性就是可以block协程, 当从一个协程往一个Channel send一个数据时, 如果没有buffer可以存, 那么就会 block 住当前协程,直到另外有地方从这个 Channel中 receive 数据。 同理 receive 也会 block 当前协程,直到有数据 send 到协程。

channel2.png

Api的设计

前面讲到的关于协程的基础设计都是基于c的实现,那么我们的上层Api是如何设计的呢?

  • oc类的封装

我们封装了 ObjC 的 COCoroutineCOChan 类,用于上层接口来调用。

  • promises

基于promises 简化了一个 COPromise的实现。

  • co_launch/await/yield/co_delay 原语

co_launch 是从线程进入协程的入口,是对COCoroutine的封装,

await/yield 原语是基于 Channel 实现的, 利用了 Channel 可以阻塞协程的特性。

co_delay 是个delay的实现,底层是使用dispatch实现。

Cancellation设计

协程的取消在 coobjc 中必需协作完成,类似NSOperation的取消。因为强行中断代码的执行,可能会造成内存泄露,如下:

id obj = ...    // Create a object

await(...)      // await, If we stop here, the cleanup phase will not execute. Cause leak.

obj = nil;      // cleanup

协程取消的另一种方式是抛异常,使调用栈回到顶层方法中,看起来是可以安全退出,但是Objective-C中的异常也会导致泄露:https://stackoverflow.com/questions/27140891/why-does-try-catch-in-objective-c-cause-memory-leak , 因此 coobjc 中的协程我们需要使用方协作完成取消。但是swift中我们就可以通过抛Swift.Error来实现Cancel.

coobjc中协作取消的原理就是,在取消一个协程的时候,我们只是标记它为 isCancelled。 如果要设计一个可取消的协程,就需要在代码中判断当前协程是否已取消来退出代码逻辑。

// in objc
COCoroutine *co = co_launch(^{
    ...
    await(...)

    // Check the current coroutine is cancelled.
    if (co_isCancelled()) {
        return;
    }
    ...
})
[co cancel];

// in swift
let co = co_launch {
    ...
    await(...)
    ...
}
co.cancel()

Swift 的设计

coswift 的底层共用了一套代码,但是在上层单独设计了一套Swift的接口。利用Swift的泛型和Error的特性,拥有相对于coobjc更佳的使用体验。

  • 使用泛型指定Channel和Promise传输的类型。
  • Cancellation 不需要协作,直接取消。
  • 使用元组