协程的设计原理与汇编实现

简介: 协程的设计原理与汇编实现


第一个问题,为什么会协程?以及协程到底解决了什么问题?

第一个同步的方式实现异步的效率

同步为什么效率低,而异步的为什么效率高?

等待没错,他需要等待,那这个等待是什么意思?

第一个案例之前跟大家讲的100万并发的案例

第二个案例异步请求的案例

现象区别:

if 1 用了 workqueue,每1000个耗时 1400

对fd直接进行写,每秒接入量5600

那现在这个工作队列他为什么在这个过程引入工作队列,他就能够提升性能呢?

他是从哪些方面原因那这个要跟他分析这个要跟大家分析,

直接进行读写

工作队列

原理类似

同步:检测io与读写io在同一个流程

异步:检测io与读写io不在一个流程

同一个流程没有切换的概念

异步性能高,同步有什么好处?

逻辑清晰,效率不高

那有没有一种方式采用同步的编程方式还同时拥有异步的性能?

那我们如何把它改进成一个同步的编程方式

那实现的原理是什么呢?

king式4元组异步连接池。

有没有一种方式只管发sql过去,不去等待他的结果,这样就造成异步做法

能不能把MySQL请求改成异步的,包括mongodb,redis,rpc都是可以改的。

这里是DNS请求

异步的:

同步的

异步结果是一起方法的,是有方法保证顺序的。

核心:同步的编程方式,要有异步的性能
那么是怎么做的呢?

两个关键的地方:

1.commit

2.callback

异步提交完了之后我们怎么改成同步的方式?

这一点就是协程是如何实现的?

对应的callback地方就是检测结果的返回有没有数据。

确确实实不在一个流程里面,callback是申请一个子线程做的

我们怎么改成一个流程里面:我们提交完就可以接受数据

看上去他们不在一个流程里面但是我们要改起来让他们在一个流程里面并且还没有副作用。

那怎么改?

在fd加入到epoll里面我们在应用层引入一个yield();

注意这个让出,让出完了之后到达哪呢?

让出完了之后到达read这个地方,解析完了之后会有一个resume恢复;

给我们感觉它没有切换的,对应的引入了两个原语操作

一个是跟大家讲的yield(),另外一个就是resume()。

让出是让出对应的流程在resume返回对应的流程
这神来之笔就是协程的实现

那么我们该如何实现yield()以及resume()?

1.yield()让出时使用longjmp跳转到read fd;resume()恢复时在使用setjmp跳回来

2.linux下面的ucontext

3.或者汇编自己跳转

yeild() 让出这里有俩个步骤,一个是longjmp() 标签一; setjmp() 标签二回来

让出到哪呢?

在讲yield/resume()之前有没有发现他们有共同的动作:是俩个上下文环境的切换,从a往b跳,从b往a跳;

看似是汇编其实代码可读性比longjmp/setjmp还有ucontext都要强很多

把读io,io没有准备就绪的这个等待取消掉,性能会无线的接近异步方式

协程究竟解决什么问题?

注意同步与异步的效率?

reactor和协程之间区别?

接收完数据,解释完数据,把数据放到sendbuff里面,再把对应的fd设置epollout

recv和send是在俩个函数本质上它还是同步;

coroutin协程是在一个函数,他们性能会差不多,只是协程会跟符合人思考方式

栈是函数调用必须要用到的东西,没有栈的话里面就根本没办法做函数调用,不推荐共享栈协程,往往共享栈协程的管理会更加复杂,

怎么确定栈大小?是自己设定的,如果你要做文件传输的话可以设置成1M或者10M,如果你只是单纯接收一些字符数据你可以开到1K或者4K;

协程的切换到底怎么实现?

**这个让出动作怎么做呢?**就是把CPU里面的寄存器组的值保存到协程A里面,这个A怎么去保存CPU寄存器的值呢?

协程切换

以X86为例,32位的,那这个寄存器的值也是32位的,它里面是有具体的值的,所以我们可以定义一个结构体去对应的保存值

保存完了之后在把B加载到寄存器这样就实现了A和B的切换;

为什么后面是定义r1,r2,r3,r4,r5呢?为了32位与64位兼容

前面这一段是保存的意思

将rsi保存到rdi里面

这里偏移0就是结构体第一个值,就是说的每一个大小是8个字节,64位里面一个指针对应的是8个字节;

movq的过程

下面这一段就是加载的过程,最后ret返回

PS:

有一些值存的是flag还有一些存的是临时变量的值我们可以不用保存;

yield 和resume的过程

可以注意到都依赖于_switch这个函数

协程的启动

协程的启动有如下三个问题:

思考一下线程是怎么启动的,启动是什么意思?

现在有一个问题入口函数的这个函数把它加到哪里?

刚刚比较好理解就是A和B是已经运行的比较好切换,如果说现在B是一个NEW的状态,那么此时他的寄存器里面保存什么值的,一加入进来怎么保持运行呢?

就是第三点这个协程运行把它推到CPU上面这个指令怎么做?

第一个把eip指针指向入口函数fun,也就是把fun的地址指向eip,然后再对应的把它的参数保存下来

还有一个东西就是关于这个栈指针这个栈esp,等下会有函数调用这个栈指针的首地址也要保存起来,

是为_exec压栈准备好的,所以要esp栈顶指针减去那个4,就是参数

把入口函数的地址换了

栈是从最底部开始指的:

核心的就是俩个指针:esp还有eip

协程到底是怎么调度的

在说明调度之前先理清一下协程是怎么定义的?

假如有十个协程我们使用什么数据结构来存储?

看一看协程源码:有红黑树还有就绪队列去存储集合

这些集合之间的关系是怎样的?

这些数据结构组织在一起,它是由多种数据结构共用的这些节点,6,7,8这几个是同时拥有的的至少在其中一个;

调度器又如何实现的

假如这里有50个协程,那么这50个coroutine我们如何组织起来,并且使用何种顺序去调度呢?

1curr_coroutine当前栈是哪个?

2.//如果是共享栈就可以放调度器里面不放协程定义2里面,

3.调度器的调度方法的具体实现可以拆出来,内核里面有cfs还有rt

协程调度里这三个集合是怎么组织起来的呢?

fd如何知道是否就绪?

在调用send还有read之前,我们将fd加入epoll管理,在引起切换yeild(),在epoll_wait()

相关文章
|
3月前
|
存储 Linux 调度
协程(coroutine)的原理和使用
协程(coroutine)的原理和使用
|
10天前
|
存储 安全 测试技术
GoLang协程Goroutiney原理与GMP模型详解
本文详细介绍了Go语言中的Goroutine及其背后的GMP模型。Goroutine是Go语言中的一种轻量级线程,由Go运行时管理,支持高效的并发编程。文章讲解了Goroutine的创建、调度、上下文切换和栈管理等核心机制,并通过示例代码展示了如何使用Goroutine。GMP模型(Goroutine、Processor、Machine)是Go运行时调度Goroutine的基础,通过合理的调度策略,实现了高并发和高性能的程序执行。
72 29
|
8天前
|
负载均衡 算法 Go
GoLang协程Goroutiney原理与GMP模型详解
【11月更文挑战第4天】Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理,创建和销毁开销小,适合高并发场景。其调度采用非抢占式和协作式多任务处理结合的方式。GMP 模型包括 G(Goroutine)、M(系统线程)和 P(逻辑处理器),通过工作窃取算法实现负载均衡,确保高效利用系统资源。
|
2月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
24 1
|
1月前
|
数据采集 调度 Python
Python编程异步爬虫——协程的基本原理(一)
Python编程异步爬虫——协程的基本原理(一)
|
1月前
|
数据采集 Python
Python编程异步爬虫——协程的基本原理(二)
Python编程异步爬虫——协程的基本原理(二)
|
1月前
|
存储 前端开发 rax
协程设计与原理(二)
协程设计与原理(二)
15 0
|
1月前
|
Java Linux Go
协程的设计原理(一)
协程的设计原理(一)
28 0
|
4月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
【7月更文挑战第15天】Python异步编程借助协程和async/await提升并发性能,减少资源消耗。协程(async def)轻量级、用户态,便于控制。事件循环,如`asyncio.get_event_loop()`,调度任务执行。异步函数内的await关键词用于协程间切换。回调和Future对象简化异步结果处理。理解这些概念能写出高效、易维护的异步代码。
58 2
|
4月前
|
编译器
8086 汇编笔记(八):转移指令的原理
8086 汇编笔记(八):转移指令的原理