四个I/O概念(阻塞、非阻塞、同步、异步)
① 阻塞:调用函数。函数没有接收完数据或没有得到结果前,就不会返回。(调用结果返回之前,当前线程会被挂起,进入非可执行状态)
例:Linux中默认所有的socket都是阻塞的。
当用户进程调用了 read()/recvfrom() 等系统调用函数,首先会进入内核空间中。当这个网络I/O没有数据的时候,内核就要等待数据的到来。而在用户进程这边,整个进程会被阻塞,直到内核空间返回数据。
当内核空间的数据准备好了,它就会将数据从内核空间中拷贝到用户空间,此时用户进程才解除阻塞的的状态,重新运行起来。
所以,阻塞I/O的特点就是在IO执行的两个阶段(用户空间与内核空间)都被阻塞了。
② 非阻塞:调用函数,函数立即返回
特点:用户进程需要不断的 主动询问 内核空间的数据准备好了没有。
③ 同步:就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。和阻塞相比,同步只是从逻辑上当前函数没有返回,它还会抢占CPU去执行其他逻辑,也会主动检测I/O是否准备好。
④ 异步:不需要等待内核完成实际对I/O的读写操作就直接返回了
I/O操作总结
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
多路复用的对比
① 多路复用的好处:单个进程就可以同时处理多个网络连接的I/O。
② select,poll,epoll本质上都是同步I/O。因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
③ 异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
select函数的缺点
① 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
② 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
③ select支持的文件描述符数量太小了,默认是1024
poll函数
poll机制和select机制类似,但是没有最大文件描述符数量的限制。
epoll函数
①:在epoll_ctl函数中会把所有的fd拷贝进内核。而不是在epoll_wait的时候重复拷贝,保证了每个fd在整个过程中只会拷贝一次。
②:在epoll_ctl函数中为每个fd都指定一个回调函数,当设备就绪会唤醒等待队列中的fd并调用回调(回调的作用是把fd加入一个就绪链表中),epoll_wait只需要判断这个就绪链表中有无数据即可。无数据的时候就无需遍历,有数据了再遍历就绪链表。
③:支持的fd上限是最大可以打开文件的数目。
总结
①:表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
②:select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善