Python协程技术的演进

引言

1.1. 存储器山

存储器山是 Randal Bryant 在《深入理解计算机系统》一书中提出的概念。

基于成本、效率的考量,计算机存储器被设计成多级金字塔结构,塔顶是速度最快、成本最高的 CPU 内部的寄存器(一般几 KB)与高速缓存,塔底是成本最低、速度最慢的广域网云存储(如百度云免费 2T )

Python协程技术的演进_第1张图片

存储器山的指导意义在于揭示了良好设计程序的必要条件是需要有优秀的局部性:

时间局部性:相同时间内,访问同一地址次数越多,则时间局部性表现越佳;

空间局部性:下一次访问的存储器地址与上一次的访问过的存储器地址位置邻近;

1.2. cpu的时间观

操作真实延迟CPU体验

执指0.38ns1s

读L1缓存0.5ns1.3s

分支纠错5ns13s

读L2缓存7ns18.2s

加解互斥锁25ns1min 5s

内存寻址100ns4min 20s

上下文切换/系统调用1.5us1h 5min

1Gbps网络传输2KB数据20us14.4h

从RAM取1M数据块250us7.5day

Ping单一IDC主机500us15day

从SSD读1M数据1ms1month

从硬盘读1M数据20ms20month

Ping不同城市主机150 ms12.5year

虚拟机重启4s300year

服务器重启5min25000year

我们将一个普通的 2.6GHz 的 CPU 的延迟时间放大到人能体验的尺度上(数据来自微信公众号 驹说码事):在存储器顶层执行单条寄存器指令的时间为1秒钟;从第五层磁盘读 1MB 数据却需要一年半;ping 不同的城域网主机,网络包需要走 12.5 年。

如果程序发送了一个 HTTP 包后便阻塞在同步等待响应的过程上,计算机不得不傻等 12 年后的那个响应再处理别的事情,低下的硬件利用率必然导致低下的程序效率。

1.3. 同步编程

从以上数据可以看出,内存数据读写、磁盘寻道读写、网卡读写等操作都是 I/O 操作,同步程序的瓶颈在于漫长的 I/O 等待,想要提高程序效率必须减少 I/O 等待时间,从提高程序的局部性着手。

同步编程的改进方式有多进程、多线程,但对于 c10k 问题都不是良好的解决方案,多进程的方式存在操作系统可调度进程数量上限较低,进程间上下文切换时间过长,进程间通信较为复杂。

而 Python 的多线程方式,由于存在众所周知的 GIL 锁,性能提升并不稳定,仅能满足成百上千规模的 I/O 密集型任务,多线程还有一个缺点是由操作系统进行抢占式调度存在竞态条件,可能需要引入了锁与队列等保障原子性操作的工具。

1.4. 异步编程

说到异步非阻塞调用,目前的代名词都是epoll 与 kqueue,select/poll 由于效率问题基本已被取代。

epoll 是04年 Linux2.6 引入内核的一种 I/O 事件通知机制,它的作用是将大量的文件描述符托管给内核,内核将最底层的 I/O 状态变化封装成读写事件,这样就避免了由程序员去主动轮询状态变化的重复工作,程序员将回调函数注册到 epoll 的状态上,当检测到相对应文件描述符产生状态变化时,就进行函数回调。

事件循环是异步编程的底层基石。

上图是简单的EventLoop的实现原理,

用户创建了两个socket连接,将系统返回的两个文件描述符fd3、fd4通过系统调用在epoll上注册读写事件;

当网卡解析到一个tcp包时,内核根据五元组找到相应到文件描述符,自动触发其对应的就绪事件状态,并将该文件描述符添加到就绪链表中。

程序调用epoll.poll(),返回可读写的事件集合。

对事件集合进行轮询,调用回调函数等

一轮事件循环结束,循环往复。

epoll 并非银弹,从图中可以观察到,如果用户关注的层次很低,直接操作epoll去

你可能感兴趣的:(epoll,python,java,操作系统,多进程)