异步编程
2022-09-29 08:55:22 0 举报
AI智能生成
异步编程内容,跨越底层内核到CPU
作者其他创作
大纲/内容
1.时代背景:现有的伪同步执行
软硬中断
CPU层/软件层抢占式调度
CPU流水线机制
2.巨人镰刀:系统调用(syscall)
定义:操作系统提供的公共API,提供与内核通信的能力
特点1:由于是跨空间(用户态<->内核态)通信,开销比纯用户态通信大
特点2:Windows与UNIX或类UNIX系统提供的API可能较大差异,一些跨
平台应用框架内部实现了与不同平台的API对接
平台应用框架内部实现了与不同平台的API对接
题外话:一般Linux和MacOS有更简单的API,需要编写的代码量
会少一些,通常(但不总是)完全相同的API在这两个系统下都是
有效的。而Windows相对更加复杂,需要你去创建更多的结构体
来传递信息(而不是直接使用内建的原生类型)
会少一些,通常(但不总是)完全相同的API在这两个系统下都是
有效的。而Windows相对更加复杂,需要你去创建更多的结构体
来传递信息(而不是直接使用内建的原生类型)
3.性能之巅:汇编(assembly)直接调用CPU的API
特点1:由于是基于CPU接口编程,相比于我们常见的
基于操作系统接口编程,能做的事情更多(更"肆无忌惮")
基于操作系统接口编程,能做的事情更多(更"肆无忌惮")
了解:大部分时候我们并不需要基于CPU接口编程,除非
你从事的编写驱动类似的相关项目(这需要熟悉CPU相关
的硬件基础知识)
你从事的编写驱动类似的相关项目(这需要熟悉CPU相关
的硬件基础知识)
4.接近真相:常见的I/O处理策略
1.使用一个系统线程处理一个I/O事件
优点:易理解、易编程、可并行
缺点:
1.单个系统线程(堆栈)占用内存不小,无法处理高并发I/O事件
2.涉及系统调用多,开销大,造成延迟高
3.系统线程切换慢
4.用户无法指定线程优先级,仅由OS调度
2.使用一个绿色线程处理一个I/O事件
优点:易编程、性能良好、可以指定线程优先级
缺点:
1.需要在用户态运行时构建一个绿色线程机制(实现成本较大)
2.基于自建的线程运行不够灵活
案例:Goroutine、Python(greenThread)
3.【最优】使用OS内核提供的EventLoop处理所有I/O事件
优点:资源利用率最高、高效率
缺点:
1.需要兼容不同OS的eventLoop实现,一次性且较大实现成本
案例:actix(Rust)、gnet(Golang)、netty(Java)、uvloop(Python),——很多是基于C写的libuv库
5.整装待发:通过syscall使用OS支持的EventLoop
不同OS对EventLoop实现方式大同小异
Windows——IOCP(I/O Complete Ports)
*unix——Epoll
*BSD——Kqueue
三种实现的大致读取socket逻辑
Epoll&Kqueue设计相似:基于已准备事件的eventloop
1.调用名为epoll_create和kqueue的syscall,创建一个eventloop
2.调用名为socket的syscall向OS申请一个socket的FD(文件描述符)
3.调用名为epoll_ctl和kevent的syscall,注册该socket的Read事件(以便在可读时收到OS的通知)
4.调用名为epoll_wait或kevent的syscall,等待来自OS的事件通知,会阻塞当前线程
5.收到通知时,根据事件类型作不同处理,一般有以下几种事件:
a.某socket的close事件,一般调用syscall.Close()关闭fd
b.client的连接请求,一般调用syscall.Accept()创建并维护新的socket
c.某socket收到数据,且已准备好被读取,一般调用syscall.Read()读取数据
a.某socket的close事件,一般调用syscall.Close()关闭fd
b.client的连接请求,一般调用syscall.Accept()创建并维护新的socket
c.某socket收到数据,且已准备好被读取,一般调用syscall.Read()读取数据
IOCP:基于已完成事件的eventloop(待优化)
1.调用名为CreateIoCompletionPort()的syscall,创建一个eventloop,
得到一个root_CP对象(complete port),这个root_CP对象是一个
专职的、负责所有其他new conn的CP对象的I/O完成事件的接收和分发处理
得到一个root_CP对象(complete port),这个root_CP对象是一个
专职的、负责所有其他new conn的CP对象的I/O完成事件的接收和分发处理
2.启动指定数量个系统线程(一般等于CPU核数),用于处理不同I/O完成事件,
如读完成/写完成)线程内部逻辑:
如读完成/写完成)线程内部逻辑:
2.1 启动一个while死循环,下面是循环内的逻辑
2.2 初始化一些马上会用到的对象,如一个socket句柄—sock,
一个CP对象—hComPort、已传输bytes长度变量—bytesTransfered、
包含数据的I/O对象—ioInfo
一个CP对象—hComPort、已传输bytes长度变量—bytesTransfered、
包含数据的I/O对象—ioInfo
2.3 阻塞调用GetQueuedCompletionStatus(参数为刚
初始化的各对象指针),该调用会在有一个I/O完成事件发生时返回,
返回时相关数据会写入上述各指针对象
初始化的各对象指针),该调用会在有一个I/O完成事件发生时返回,
返回时相关数据会写入上述各指针对象
2.4 当OS回调上述函数时,判断若是读完成事件:
a.若bytesTransfered=0,表示传输EOF,需closesocket()
b.若不为0,则从ioInfo直接取出数据(此时数据已从内核态copy到用户态,
无需再执行系统调用读取数据),并作相应处理,最后执行
系统调用WSASend()往socket写入数据;(WSASend也是非阻塞调用)
a.若bytesTransfered=0,表示传输EOF,需closesocket()
b.若不为0,则从ioInfo直接取出数据(此时数据已从内核态copy到用户态,
无需再执行系统调用读取数据),并作相应处理,最后执行
系统调用WSASend()往socket写入数据;(WSASend也是非阻塞调用)
注:IOCP是在数据拷贝完成后通知我们,
区别于Epoll&Kqueue在数据准备好拷贝时通知我们
区别于Epoll&Kqueue在数据准备好拷贝时通知我们
2.5 若是(server自己的)写完成事件,一般没有特别处理,可以简单记录log
2.6 再次执行系统调用WSARecv(sock, &ioInfo...)继续等待client发送数据
2.7 重复while循环
3.执行系统调用WSASocket()申请一个socket句柄—root_sock,并bind()地址
然后开始Listen(root_sock); Listen是非阻塞调用
然后开始Listen(root_sock); Listen是非阻塞调用
4.在主线程中启动一个while死循环,循环内部逻辑:
4.1 阻塞系统调用accept(),等待client连接
4.2 当有client连接时,accept()返回一个socket句柄—new_sock,
并将其作为参数再次调用CreateIoCompletionPort(new_sock, root_CP...)
并将其作为参数再次调用CreateIoCompletionPort(new_sock, root_CP...)
4.3 初始化一个PER_IO_DATA类型的ioInfo对象
4.4 将new_sock和ioInfo对象作为参数执行系统调用WSARecv(new_sock, &ioInfo...),
此处为非阻塞调用;(注:收到读完成事件的处理逻辑在第二步启动的线程内完成)
此处为非阻塞调用;(注:收到读完成事件的处理逻辑在第二步启动的线程内完成)
4.5 重复while循环
6.上阵冲锋:Node.js
1.缘起:作者的初衷是创建一个便于实现高性能的web服务器的服务端框架
2.设计:需要一个低成本、高效率实现能够支撑高并发I/O请求的模型
3.定稿:采用操作系统支持的基于事件循环的单线程非阻塞I/O模型(调用libuv)
并使用C++在V8引擎之上构建服务端的JS运行时
并使用C++在V8引擎之上构建服务端的JS运行时
4.选择载体语言JS,理由是
JS是高级语言,具备其他低级语言不支持的特性
如闭包、函数式编程、语法简洁性
如闭包、函数式编程、语法简洁性
同事件循环模型核心一致,JS也是单线程运行和事件驱动编程,天生契合
JS本是前端语言,若能应用到服务端,则可以重用部分代码;
并在项目中统一前后端语言
并在项目中统一前后端语言
Chrome提供的V8引擎能够直接解释(编译)JS为机器码,大大提高运行性能
JS的拥趸者本来就多
5.内部任务处理方式
I/O密集型任务,由跨平台的libuv库处理
CPU密集型任务,由线程池处理(Node的C++扩展都是使用线程池执行)
0 条评论
下一页