资讯专栏INFORMATION COLUMN

【协程原理】 - 协程不过是用户态的线程

pcChao / 3508人阅读

摘要:也就是现代操作系统的虚拟内存空间。有在两个进程之间切换状态的时候,需要把内存的映射关系调整过来,否则虚拟内存的地址是无法对应到正确的物理地址的。但是原理是非常类似的。协程与线程的区别在于,协程的是在完全在用户态,由语言的或者是库来完成的。

TL;DR

笔者最美好的记忆来自于早年在6502 cpu的cc800上写汇编的年代, 那个时代的计算机甚至没有操作系统,也没有实模式等保护机制。在6502上写汇编应用其实非常简单,系统会把bin文件加载到一个固定的内存地址中,cpu会固定地从一个特定的位置开始执行。然后cpu就按照你提供的机器指令开始一条一条的执行。在高级语言中的“函数调用”的概念,在汇编里主要体现为两个寄存器。寄存器是cpu内部临时保存数据的区域,相当于高级语言里的变量。但是有一个寄存器是特殊的,它存放了cpu当前正在执行的指令的内存地址(Instruction Register)。一个高级语言中的函数一般会被编译成指令存放在一段连续的内存空间中(data segment)。那么所谓函数执行到了第几行这样的信息其实就是保存在这个Instruction Register中的。另外一个很特殊的寄存器是Stack Register,它其中存放的内存地址指向的内存区域用于函数之间传递参数和返回值,以及存放一个函数内的局部变量。如果不考虑现代计算机cpu中各种各样其他存放中间结果的寄存器,理论上保存了Instruction Register(执行到哪儿了)和Stack Register(堆栈上的变量)就保存了一个函数的当前执行状态,分别是函数当前执行到了哪,以及这个函数局部变量所代表的当前state。

事实上,操作系统的几个关键切换也是这么来完成的。操作系统提供了两个执行态,一个是用户态,一般我们的代码都是执行在用户态的。另外一个是内核态,像驱动程序之类的代码会用各种方式被加载到操作系统内部执行在内核之中。内核态里的代码可以完全控制CPU的I/O中断,从而可以和外部设备交互。用户态的代码属于受限代码,必须把I/O请求通过syscall交由运行在内核态的操作系统来完成。当一个cpu的核在执行用户态代码时,其寄存器里存放的状态是你的应用的代码的状态,但是应用要进行I/O操作的时候,cpu要被切换到内核的代码里去执行内核态的代码。这里就需要进行一次context switch,所谓context switch其实原理不会比把寄存器的值存到内存的一个地方,等回来的时候再把内存中临时保存的值加载回寄存器复杂多少。

操作系统还有一个需要进行context switch的地方,那就是在协程与协程之间。操作系统在执行一个ELF或者PE的可执行文件的时候,对于这个可执行文件内的汇编代码来说,整个内存寻址空间是独立的。也就是1.exe的执行状态完全无法感知到2.exe的执行状态的内存。也就是现代操作系统的虚拟内存空间。有cpu在两个进程之间切换状态的时候,需要把内存的映射关系调整过来,否则虚拟内存的地址是无法对应到正确的物理地址的。一个进程内的两个线成切换的时候,要稍微简单一些,只需要把当前线成正在执行的位置和栈做切换就可以了。

无论是操作系统做user/kernel的switch,还是process/process,thread/thread的switch,其实现方式都是大同小异的。通过把“当前执行状态”这样的一个抽象概念落实为一个具体的数据结构存储起来,然后指挥cpu在不同的场合加载不同的数据恢复不同的“当前执行状态”。

在高级语言中,一个函数正在执行的位置以及其状态,内部都可以有一个抽象的表达方式。有的高级语言直接被编译成原生的机器码,那么其执行状态的表述就和操作系统的context switch的context非常类似。有的高级语言自身执行在一个虚拟机之上,那么其context的表述可能是虚拟机的instruction register和stack register,而不是80x86这样原生的机器的物理寄存器。但是原理是非常类似的。

取决于语言设计者的觉悟,有的语言会把这种表达执行状态的能力直接提供出来,让一个函数在执行过程中可以把当前状态保存,然后把执行权交给另外一个函数执行,等那个函数放弃执行权回来的时候再把保存的状态恢复。这也就是所谓的协程(co-routine)。协程与线程的区别在于,协程的context switch是在完全在用户态,由语言的runtime或者是库来完成的。而线程的context switch则是操作系统来完成的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/45309.html

相关文章

  • PHP协程

    摘要:协程协程就是用户态的线程要理解是什么是用户态的线程,必然就要先理解什么是内核态的线程。记住,不是协程,而是协程需要借助的特性来实现。 协程 协程就是用户态的线程 要理解是什么是用户态的线程,必然就要先理解什么是内核态的线程。 内核态的线程是由操作系统来进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换回上一个线程,并恢复上下文。 协程也...

    SolomonXie 评论0 收藏0
  • 【Go语言学习】2019-04-24 协程初步讨论与简单扩展

    摘要:它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。而可以理解为一种语言的协程。线程轻量级进程,,是程序执行流的最小单元。一个标准的线程由线程,当前指令指针,寄存器集合和堆栈组成。其实就是或者等语言中的多线程开发。 grape 全部视频:https://segmentfault.com/a/11... 原视频地址:https://biglive.xueersi.c...

    SnaiLiu 评论0 收藏0
  • PHP下的异步尝试二:初识协程

    摘要:如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部时间而不与其他任务共享。多个操作可以在重叠的时间段内进行。 PHP下的异步尝试系列 如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的异步尝试四:PHP版的Promise ...

    MudOnTire 评论0 收藏0
  • PHP回顾之协程

    摘要:本文先回顾生成器,然后过渡到协程编程。其作用主要体现在三个方面数据生成生产者,通过返回数据数据消费消费者,消费传来的数据实现协程。解决回调地狱的方式主要有两种和协程。重点应当关注控制权转让的时机,以及协程的运作方式。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 sess...

    Java3y 评论0 收藏0
  • 三年半Java后端面试经历

    摘要:经过半年的沉淀,加上对,和分布式这块的补齐,终于开始重拾面试信心,再次出征。面试官提示没有提到线程的有内核态的切换,程只在用户态调度。三面综合技术面这面面的是阵脚大乱,面试官采用刨根问底的方式提问,终究是面试经验不够,导致面试的节奏有点乱。 经过半年的沉淀,加上对MySQL,redis和分布式这块的补齐,终于开始重拾面试信心,再次出征。 鹅厂 面试职位:go后端开发工程师,接受从Jav...

    kviccn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<