同步阻塞 BIO
当用户程序的线程调用 read 获取网络数据的时候,首先这个数据得有,然后这个数据需要拷贝到内核中,再被拷贝到用户空间内,这整一个过程用户线程都是被阻塞的。
优点很明显,简单。调用 read 之后就不管了
缺点也很明显,一个线程对应一个连接,一直被霸占着,这就有点浪费了
同步非阻塞NIO
在没数据的时候,用户程序可以不再阻塞等着,而是直接返回错误,用户程序会通过轮询操作,不断发起 read 调用,直到内核中的数据拷贝就绪,才会停止发起read 调用
不过在数据从内核拷贝到用户空间的时候,这段时间内用户程序是出于阻塞状态的。如果服务器需要处理海量的连接,那么就需要有海量的线程不断调用,上下文切换频繁,CPU 也会忙死
I/O 多路复用
I/O 多路复用模型,只用一个线程查看多个连接是否有数据已准备就绪。
仅需往 select 注册需要被监听的连接,由 select 来监控它所管理的连接,是否有数据已就绪,如果有则可以通知别的线程来read 读取教据,和之前的一样,还是会阻塞用户线程。
这样一来就可以用少量的线程去监控多条连接,减少了线程的数量,所谓的多路指的是多条连接,复用就是用一个线程监控多条连接。
信号驱动式I/O
I/0多路复用的 select 虽然不阻塞了,但是它得时刻去查询是否有数据已经准备就绪,信号驱动I/O能实现由内核告知数据已准备就绪
但基本不用信号驱动 I/O
因为应用通常用的都是 TCP 协议,而 TCP 协议的 socket 可以产生信号事件有七种。不仅仅只有数据准备就绪才会发信号,其他事件也会发信号,而这个信号又是同一个信号,所以我们的应用程序无从区分到底是什么事件产生的这个信号。
如果你的应用程序用的是 UDP 协议,那是可以的,因为 UDP 没这么多事件。
异步 AIO
思路很清晰: 让内核直接把数据拷贝到用户空间之后再告知用户线程
所以异步 AIO 其实就是用户线程调用 aioread,然后包括将数据从内核拷贝到用户空间那步,所有操作都由内核完成
举例
BIO:用烧水壶烧水,自己站在边。自己盯着。一旦开了,你把水倒到杯子里面
NIO: 用烧水壶烧水。你就去打2分钟游戏。然后回来看一下开了没。没开就又去打2分钟游戏。
多路复用: 找了个专门帮人烧水的邻居。他给很多人烧水。他给你烧水,你就跑回家玩了。水开了,他就电话打你。你赶紧来拿。
信号量: 你去烧水房。是自动的,你烧上,人就可以走。烧开了就自动响你门铃,但是有时候客人来了也响门铃。你就搞不清楚到底是啥情况
5.异步io: 自动烧水壶,最后还要给你倒杯子里面,通知你喝
那为什么常用的还是I/O多路复用,而不是异步I/O
因为 Linux 对异步I/O 的支持不足,即还未完全实现,像 Tomcat 都实现了AIO 的实现类,实际上底层实现是用 epoll 模拟实现的。
而 Windows 是实现了真正的 AIO,不过我们的服务器一般都是部署在 Linux 上的,所以主流还是 I/O 多路复用。