从coobjc理解协程

前言:

自己对协程的概念的理解,源于coobjc的开源。文章参考了其他人对于协程的理解,加以融合贯通,希望能对不了解协程的人给予理解上的帮助。

协程的概念可能很多人不熟悉,第一次听到这个词,可能是这样:

什么是协程.jpg

完整的漫画在这里。

看完漫画,可能对协程有了初步的了解,至少知道除了订机票,还能编程~

理解协程,要先明确一个概念:用户态线程

引用一段话加以说明:

  • 一开始大家想要同一时间执行那么三五个程序,大家能一块跑一跑。特别是UI什么的,别一上计算量比较大的玩意就跟死机一样。于是就有了并发,从程序员的角度可以看成是多个独立的逻辑流。内部可以是多cpu并行,也可以是单cpu时间分片,能快速的切换逻辑流,看起来像是大家一块跑的就行。
  • 但是一块跑就有问题了。我计算到一半,刚把多次方程解到最后一步,你突然插进来,我的中间状态咋办,我用来储存的内存被你覆盖了咋办?所以跑在一个cpu里面的并发都需要处理上下文切换的问题。进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。
  • 后来一电脑上有了好几个cpu,好咧,大家都别闲着,一人跑一进程。就是所谓的并行。
  • 因为程序的使用涉及大量的计算机资源配置,把这活随意的交给用户程序,非常容易让整个系统分分钟被搞跪,资源分配也很难做到相对的公平。所以核心的操作需要陷入内核(kernel),切换到操作系统,让老大帮你来做。
  • 有的时候碰着I/O访问,阻塞了后面所有的计算。空着也是空着,老大就直接把CPU切换到其他进程,让人家先用着。当然除了I\O阻塞,还有时钟阻塞等等。一开始大家都这样弄,后来发现不成,太慢了。为啥呀,一切换进程得反复进入内核,置换掉一大堆状态。进程数一高,大部分系统资源就被进程切换给吃掉了。后来搞出线程的概念,大致意思就是,这个地方阻塞了,但我还有其他地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不用特别麻烦的切换页表、刷新TLB,只要把寄存器刷新一遍就行,能比切换进程开销少点。
  • 如果连时钟阻塞、 线程切换这些功能我们都不需要了,自己在进程里面写一个逻辑流调度的东西。那么我们即可以利用到并发优势,又可以避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是用户态线程。
  • 从上面可以看到,实现一个用户态线程有两个必须要处理的问题:一是碰着阻塞式I\O会导致整个进程被挂起;二是由于缺乏时钟阻塞,进程需要自己拥有调度线程的能力。如果一种实现使得每个线程需要自己通过调用某个方法,主动交出控制权。那么我们就称这种用户态线程是协作式的,即是协程。

了解了用户态线程,就对理解协程更近了一步

协程它能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。普通过程(函数)可看成这个特殊过程的一个特例:只有一个状态,每次进入时局部状态重置。

下面结合coobjc理解一下协程如何控制,切换状态:

上面说的调用状态,我理解成线程的寄存器状态,协程的操作其实就是记录寄存器状态到对应的上下文中,继而通过上线文,控制协程的操作。在coobjc中对应的应该就是coroutine_ucontext_t
对应的启动,切换控制权,恢复就是通过操作coroutine_ucontext_t,核心API如下
coobjc 协程实现的核心在
core/coroutine_context 中,点开头文件,可以看到

//获取协程上下文
extern int coroutine_getcontext (coroutine_ucontext_t *__ucp);
//设置协程上下文
extern int coroutine_setcontext (coroutine_ucontext_t *__ucp);
//设置协程上下文
extern int coroutine_begin (coroutine_ucontext_t *__ucp);
//创建协程上下文
extern void coroutine_makecontext (coroutine_ucontext_t *__ucp, IMP func, void *arg, void *stackTop);

这里使用汇编实现了上下文的获取、设置:

#if defined(__arm64__) || defined(__aarch64__)

.text
.align 2
.global _coroutine_getcontext
_coroutine_getcontext:
    stp    x18,x19, [x0, #0x090]
    stp    x20,x21, [x0, #0x0A0]
    stp    x22,x23, [x0, #0x0B0]
    stp    x24,x25, [x0, #0x0C0]
    stp    x26,x27, [x0, #0x0D0]
    str    x28, [x0, #0x0E0];
    stp    x29, x30, [x0, #0x0E8];  // fp, lr
    mov    x9,      sp
    str    x9,      [x0, #0x0F8]
    str    x30,     [x0, #0x100]    // store return address as pc
    stp    d8, d9,  [x0, #0x150]
    stp    d10,d11, [x0, #0x160]
    stp    d12,d13, [x0, #0x170]
    stp    d14,d15, [x0, #0x180]
    mov    x0, #0                   
    ret

.global _coroutine_begin
_coroutine_begin:
    ldp    x18,x19, [x0, #0x090]
    ldp    x20,x21, [x0, #0x0A0]
    ldp    x22,x23, [x0, #0x0B0]
    ldp    x24,x25, [x0, #0x0C0]
    ldp    x26,x27, [x0, #0x0D0]
    ldp    x28,x29, [x0, #0x0E0]
    ldr    x9,     [x0, #0x100]  // restore pc into lr
    mov    x30,   #0;
    ldr    x1,      [x0, #0x0F8]
    mov    sp,x1                  // restore sp
    ldp    d8, d9,  [x0, #0x150]
    ldp    d10,d11, [x0, #0x160]
    ldp    d12,d13, [x0, #0x170]
    ldp    d14,d15, [x0, #0x180]
    ldp    x0, x1,  [x0, #0x000]  // restore x0,x1
    ret    x9

.global _coroutine_setcontext
_coroutine_setcontext:
    ldp    x18,x19, [x0, #0x090]
    ldp    x20,x21, [x0, #0x0A0]
    ldp    x22,x23, [x0, #0x0B0]
    ldp    x24,x25, [x0, #0x0C0]
    ldp    x26,x27, [x0, #0x0D0]
    ldp    x28,x29, [x0, #0x0E0]
    ldr    x30,     [x0, #0x100]  // restore pc into lr
    ldr    x1,      [x0, #0x0F8]
    mov    sp,x1                  // restore sp
    ldp    d8, d9,  [x0, #0x150]
    ldp    d10,d11, [x0, #0x160]
    ldp    d12,d13, [x0, #0x170]
    ldp    d14,d15, [x0, #0x180]
    ldp    x0, x1,  [x0, #0x000]  // restore x0,x1
    ret    x30

最后列举一点协程的优点

  • 跨平台
  • 跨体系架构
  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销

协程没有线程的切换,省去了切换线程需要的开销,这是协程对比多线程的优势。协程不是多线程,自然也舍去了加锁,解锁的操作。但是协程通过代码的控制逻辑,中断,恢复任务。

参考:

coobjc

协程的好处有哪些?

阿里开源 iOS 协程开发框架 coobjc源码分析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容