1)为什么要有协程?
2)客户端的同步和异步
1.客户端的同步:发送一个请求,等待服务器端的返回。如:请求Redis,等待返回value
缺点: 每一秒钟客户端请求服务器的数量不会很多。
2.客户端的异步:不停的提交。
结论:客户端使用异步,速度远远快于同步。
3)king式四元组:
1.init
2.commit
3.callback
4.destroy
4)服务器端的同步与异步(以增加1000连接为例子)
(1)同步: 5.6s
epoll进行读写是否就绪的监听,并且recv和send在一个流程里面,则叫同步。
(2)异步: 0.8s
实现:epoll监听到读写事件,包装为任务,扔到线程池中,
线程池中进行recv与send。
定义:epoll事件感应和recv、send不在一个线程中,这叫异步。
5)2个口语词
(1)异步IO: AIO的模式,有数据了直接回调。
(2)IO异步操作:多线程异步\多线程同步
6)同步/异步 与 协程有什么关系?
(1)知乎上的回答:用同步的编程方式,异步的。。。
(2)同步和异步是在形容2者之间的关系。 如果找不到2者,请用:阻塞与非阻塞。
同步的优点:读完后解析,符合人线性化的思维。
异步:
多个客户端连接服务器。
一个客户端发出一个IO事件:“你好”。
检测一个IO事件,服务器把这个IO事件push到另外一个线程里面。
这时,再次发送一个“你好1”。
又收到。。。多个线程共用一个fd的线程--》脏数据。--》对fd进行加锁--》不利于我们代码写逻辑。
同步的编程方式,更符合我们的思维,更加的直观。但是同步的性能不高,没有异步的好。
目的:有没有同步的编程方式,却有异步的性能?
协程是如何解决这个问题的?
7)目标:
(1)现在:客户端一下子能提交多个请求。
慢慢的等待服务器端的返回。--》返回是在另外一个callback处理。
(2)目标:多个请求,看起来是同步的等待返回。 但是却拥有异步的性能。==》如何实现?
8)实现跳转的3种方法(类似于goto的语句,是函数内部跳用)
1.setjump/longjmp: 跨越函数栈的跳转。 -->c的标准接口
2.ucontext: 系统上下文的跳转。-->linux的接口
3.自己用汇编来实现:跳转。--》go的底层实现也是汇编。
封装为2个原语操作:
yield: jump-->pos (commit后,让出cpu,依赖于epoll,到callback函数里面去,callback执行完后,就resume回到执行过程。)
resume: jump-->back ==》什么时候调用是由epoll控制的。
switch: 采用汇编来实现。 用几个寄存器保存上下文。
9)如何实现跳转?
2个CPU的上下文切换:
一个CPU,上面保存了16个寄存器。
线程1在运行。 保存线程1里面寄存器的值(store),加载线程2里面的值(load),这就是线程上下文的切换。
eax,ebx...
10)如何保存寄存器里面的值?
11)协程的出现:
实现这样一个框架:降低编程难度。同步的写法,异步的性能。 让出CPU,同时依赖于epoll,实现流程的切换。
================
1)封装为协程后的
从客户端提交请求-->callback函数返回数据,是在一个调度单元里面的。
epoll时,让出cpu,让epoll检测是否有数据:
有数据:recv,recv完毕后,切换回来。
没有:
2)之前:
主线程一直commit,另外一个线程等待结果的返回。
现在: 提交请求,让出cpu--》切换到另外一个线程的。
IO密集型的情况下,协程可以完全替代线程。
服务器计算结果,协程不能代替线程。不是强计算。
3)如何定义 ‘协程’ 结构体
1.cpu_ctx
2.status(ready,running,defer)
3.func: 入口函数
4.arg
5.stack栈空间: 为什么是必要的? ==》让每一个协程 “独立” 的根本。==》sp指针--》指向的位置 就是栈--》接下来CPU帮助你处理。
是独立的,而不是共用的。
6.stack_size
一个调度器,调度N多个协程。每一个有着不同的状态。
当前运行着是哪个协程。 多个就绪,多个休眠--》但是只有一个在运行。
4)'调度器'的定义
1.cur_ctx
2.set<ready>
3.set<defer>
4.epfd
5)
同步: 1个请求,一个回应。
异步: 发一个请求,再发一个请求。
同步写法,异步性能: 发一个请求,让出CPU,跳转到接收的地方。判断IO是否有数据,没有数据,再切换回来。
一定是有很多的异步请求,才有必要。
提交请求的地方,叫做让出CPU,跳到:recv这。。处理完,再跳回来。 ==》其实表面是同步的,底层依然是:异步的。
6)
结构。调用和关闭是托管使用的。
入口函数。
协程的参数
线程: id, NULL, server, port
coroutine_create: 分配一个couroutine,并且加入到就绪队列ready里面。 并不是开始运行了,只有调度器调度到此能运行。
run: 调度器的开始执行。
7)coroutine:协程
1.cpu上下文,寄存器组
2.协程的入口函数
3.参数
4.栈大小
5.处于一个什么状态(就绪N、等待N、休眠N、退出N--》集合)
6.就绪集合--》一个元素。 队列 --》
7.等待集合--》一个元素。 队列。(超时时间,则可以用红黑树)
8.休眠集合--》一个元素。 红黑树 --》 等待某个条件的满足。 -->一次性超出多个。
一个协程,在多个集合里面是完全有可能的。
8)Timer的实现
最小堆、红黑树、时间轮
9)调度器的定义
1.当前运行的哪个协程。--》方便我们yield(sched->cur, sched->cur->next)操作。
2.epfd: 整个协程调度的最原始的动力。
全局变量--》加到调度器里。
每个协程特有的加到协程里面。
10)
ntyco提供的api。
schedule_run调度的方法。
有多个地方调用recv,则切换跳到另外一个recv的地方。 直到找到fd准备就绪了。--》这样让recv函数本身就是异步的。==》同步的recv改为异步的recv。
11)核心点: nty_poll_inner
将fd加到epoll里面。--》
12)跳转的核心的点:
nty_coroutine_yield
13)在recv之前,将fd加入到全局epoll里面。 nty_recv、nty_send、nty_connect、nty_accept
1.fd--》epoll
2.yield ==>
调度器:master主线程,不属于任何一个coroutine co1 co2 co3 co4
向左是yield,,向右是resume
并不是由线程1到线程2,而是线程1切换到调度器里面。 然后调度器再切换到线程2.
调度器里面执行的全部是resume函数。
在协程里面执行的全部是yield函数。
14)没有东西加入到epoll里面,则会阻塞。
先create,再调用schedule_run, 否则就直接退出了。
15)co被创建出来的时候,一次都没有运行的时候,是如何被第一次运行的,入口点在哪里?
co->ctx.eip=_exec;
IO密集型的操作,底层核心是epoll在驱动着调度器调度。
=========
多个IO可读写的话:处理完一个,返回到调度器,调度器再调度下一个。
==========多核
1.借助线程。
a.所有的线程共用一个调度器;
b.每个线程一个调度器;
2.借助进程。
=================CPU的粘合
每个CPU上都有一个调度队列。
绑定CPU:这个进程只在这个CPU上执行。
================
其实协程的性能,并不比:epoll+多线程的方式高,毕竟协程内部还是依赖于epoll。
使用最主要的原意是:简单,容易维护!