协程库项目实现1
github项目链接:GitHub - Sutdown/coroutinelib: coroutine lib
前言(碎碎念一下)
之前学着写了下lsm tree
,学到的东西远比想象的要多,从九月份开始到现在,大概两三个月时间,和六级备考,一些课程期末作业并行,日均大概在10+h。两个月前写了个想法自嘲是虚假的科班选手,也没做,起码大学的前两年实在没走在学习cpp
的正确道路上,不过感谢学校了,以前似懂非懂用的工具写的代码如今都慢慢理解了曾经是在做什么。这几个月从c++
新特性,到linux
内核,到看各种源码,到自己上手从0开始写着项目,深刻感觉此时彷佛才真正是开始入门。
“道阻且长,行则降至。”
11月马上也要结束了,害,时间过的真快啊,之前还计划着写这个之前看看libco
,这个想法往后推迟吧。不过写协程库之前还是得大致对协程有个简单了解的,这篇文章着重于协程介绍。
正文篇1 个人理解
进程,线程,协程之间的关系,出现的原因,历史渊源等
进程是计算机中最先出现的概念,也就是当程序执行一个可执行文件时,cpu从前往后执行这个程序的文件,这个过程也就被成为进程。
最刚开始我们默认单核cpu只能执行一个进程,但当进程被中断时,cpu会阻塞进程等待中断返回,中断一般由内核中的中断处理程序响应,一般中断会尽可能的短且块,这样尽量减少正常进程运行调度的时间。关于中断,中断其实是分为软中断和硬中断的。
- 硬中断是由CPU给物理引脚施加电压变化实现的。一般位于linux内核实现的上半部分,快速处理一些简单的部分,之后发出软中断。
- 软中断是通过给内存中的一个变量赋予二进制值以标记有软中断的发生。软中断主要由ksoftirqd(kernel software interrupt queue daemon内核软中断队列守护)线程处理会用ringbuffer收包然后交给各个协议层处理,对于TCP则是socket队列。
- 软中断一般是由中断处理程序和软中断线程的情况下发生,很类似于单一生产者消费者模拟,和环形缓冲区的经典应用场景相匹配。软中断的过程为,硬件中断触发时,将接收的数据包放入环形缓冲区,协议栈读取环形缓冲区进行进一步处理。其优点在于连续内存的使用使得能够快速读取,固定容量无需动态分配内存,可以使用无锁实现高效并发(生产者和消费者线程的指针独立,不存在竞争)。
回到进程,为了高效利用中断时的时间,因此产生了并发。并发时,一个进程切换到另一个线程运行,这个成为进程的上下文切换,进程的上下文切换其实不仅包含了虚拟内存,栈,全局变量等用户空间的资源,还包含了内核堆栈,寄存器等内核空间的资源。
由于进程之间无论是切换,还是数据其实都是单独的,如果想要并发运行或者共享数据都是极为复杂的,因此提出了线程的概念,线程之间可以并行运行,也可以共享相同的地址空间,同时将线程设置为进程当中的一条执行流程,也就是进程的下一个级别。
因此我们常说,进程是资源分配的单位,线程是cpu调度的单位
对于进程和线程,其实还有很多可以讨论的问题,比如进程间的通信方式;多线程需要共享数据,那么如何避免冲突;进程最多可以创建多少个线程;线程崩溃进程也会崩溃吗;死锁,悲观锁,乐观锁,共享锁,排他锁,这么多锁的说法到底是怎么一回事。这些如果讲起来就有点偏离本篇协程的主题了,因此估计会再写一篇文章吧,留个悬念。
协程的本质是什么,为了解决什么事情
下面2中有句话说的很合适,协程 的发明是为了解决线程的 Concurrency(并发),线程 的发明解决进程的 Parallelism(并行)。
协程其实有点像线程下的级别,线程在解决某个任务时,仍然会有等待的时间,这段等待可能是线程加载时的IO操作,基本不消耗cpu资源,如果这段时间用于线程切换的话,可以,但是存在一定的开销。
协程的创建并非操作系统层面,不涉及内核调度,一般直接用编程语言实现,属于用户态。这个过程类似于线程内实现的并发,协程作为一种特殊的subroutine,可以在执行一半时暂停,这样在遇到IO之类不消耗cpu的操作时,可以将其挂起,继续计算其它任务,充分利用cpu资源。
sylar的协程实现使用了非对称模型,且保证子协程不能再创建新的协程,即协程不能嵌套调用,子协程只能与线程主协程进行切换,这种模型简单,非常容易理解。
正文篇2 摘录
(来源见参考链接)
协程 的发明主要是为了解决 Concurrency(并发) 问题,
线程 的发明主要解决的 Parallelism(并行) 问题。
协程简单介绍
“其实不应该把协程和多线程做类比,协程更多的是取代异步状态机的数据结构,如果明确这点,就能够清晰使用场景了。” —— from libco 的实现者
协程是一类程序组件,它是对子过程概念的泛化,并且是属于非抢占的多任务处理。
它的两个关键概念:
- 泛化的子过程
- 非抢占的多任务处理
每个协程在创建时都会指定一个入口函数,这点可以类比线程。协程的本质就是函数和函数运行状态的组合 。协程和函数的不同之处是,函数一旦被调用,只能从头开始执行,直到函数执行结束退出,而协程则可以执行到一半就退出(称为yield),但此时协程并未真正结束,只是暂时让出CPU执行权,在后面适当的时机协程可以重新恢复运行(称为resume),在这段时间里其他的协程可以获得CPU并运行,所以协程被描述称为“轻量级线程”。
协程能够半路yield、再重新resume的关键是协程存储了函数在yield时间点的执行状态,这个状态称为协程上下文。协程上下文包含了函数在当前执行状态下的全部CPU寄存器的值,这些寄存器值记录了函数栈帧、代码的执行位置等信息,如果将这些寄存器的值重新设置给CPU,就相当于重新恢复了函数的运行。
单线程环境下,协程的yield和resume一定是同步进行的,一个协程的yield,必然对应另一个协程的resume,因为线程不可能没有执行主体。并且,协程的yield和resume是完全由应用程序来控制的。与线程不同,线程创建之后,线程的运行和调度也是由操作系统自动完成的,但协程创建后,协程的运行和调度都要由应用程序来完成,就和调用函数一样,所以协程也被称为“用户态线程”。
协程理论
协程就是函数
协程是函数的一种泛化,允许暂停函数并稍后恢复。
对称协程和非对称协程
- 对称协程,协程可以不受限制地将控制权交给任何其他协程。
- 非对称协程,是指协程之间存在类似堆栈的调用方-被调用方关系。
对称协程更灵活,非对称协程实现更简单。
有栈协程和无栈协程
- 有栈协程:用独立的执行栈来保存协程的上下文信息。有栈协程的核心是io的异步化,要hook常用io.
- 无栈协程:它不需要独立的执行栈来保存协程的上下文信息,协程的上下文都放到公共内存中。
有栈协程
- 独立栈:独立栈,也就是每个协程的栈空间都是独立的,固定大小
- 共享栈:共享质就是所有的协程在运行的时候都使用同一个栈空间,每次协程切换时要把自身用的共享栈空间拷⻉。
独立栈和共享栈