通过之前的学习, 我们已经知道,epoll 在操作系统内核中其实就是通过 异步事件驱动(硬件中断通知) 实现了 “就绪事件列表的通知” 。只不过对于应用层来说,你是同步阻塞在 epoll_wait() 这里,所以应用层并不是异步,只是享受了 epoll 带来的便利,避免你做大量的无用循环去消耗 CPU 做忙等待。
那这种依赖 epoll IO 多路复用做的异步事件驱动如何提高并发能力?
假设用户要查询我们的商品列表页,首先对于用户来说, 无论你底层采用的是同步还是异步,用户都是在同步等待你这个列表页数据的加载完成 。(看上去好像对用户来说,直观上没什么感受......)
假设这个列表页的接口查询非常慢,如果我们底层采用的是传统的 单线程 + 同步阻塞 的方式,那么只要这个用户没有访问完成,其他用户都无法访问,因为只有一个线程,而且当前线程是被同步阻塞的。
你要想让更多用户访问,除非你使用多线程,但是线程再多也架不住用户的数量多啊,每个用户占用一个线程显然不现实。如果接口阻塞事件不长那还好,我们可以用线程池,也就是一个线程某个用户用完后,另一个用户还可以复用, 但是本质上系统的 并行能力 是由你线程池的最大线程数决定的,所以并行量是死的,如果接口快的话,并发 是会高的,但接口慢的话,每个线程都被阻塞的话,说到底并发量就取决于线程池的线程数量所决定的 极限并行量 。
但是如果底层采用的是异步事件驱动,由于你底层是基于 epoll IO 多路复用,你的线程就能处理很多很多的用户请求了,你的线程只用通过 事件循环(Event Loop) while(true) 的方式去监听 epoll_wait 是否有就绪事件,当然,这里的事件可以是任何事件,都可以一并由单个线程进行监听。
实际上,异步模型通常使用一个事件循环线程(也就是主线程)来处理所有的事件。但是,为了充分利用多核CPU,可能会启动多个事件循环线程(也就是多个Worker进程/线程),每个线程都运行一个事件循环。但是,即使只有一个事件循环线程,它也可以处理成千上万的连接。
所以一个线程就可以搞定成千上万的并发,多个线程承担的并发量就会更大。
两个基石
应用层的事件循环(Event Loop) + epoll 内核层的异步事件驱动的“就绪通知” 。
在我看来是应用程序称为 "基于事件驱动"的核心。在此基础上,应用程序增加了线程模型,协程,响应式等一些编程模型,但本质上是基于这两个特点的。但万变不离其宗: 所有"花哨"的功能最终都要回归到那个最基础的 事件循环 和 epoll 等待。