网上有很多关于同步阻塞的资料,阐述的方式却有所区别,本人在阅读相关博客后,记录一下自己的理解

阻塞,非阻塞,同步,异步,IO多路复用

​ 在进行网络IO时,会涉及到用户态和内核态,并且会在用户态和内核态之间完成数据交换,那么我们将IO操作分为两段

  • 用户态等待内核态将数据准备好

  • 将数据从内核态拷贝到用户态

    而同步,异步,阻塞,非阻塞的区别,就是在于对这两个阶段不同的处理方式进行分类

同步阻塞

  • 第一阶段,当用户态调用read操作,这时候内核还没有准备好数据,那么用户就会一直阻塞等待,直到有数据返回
  • 第二阶段,当内核准备好数据之后,用户态继续等待内核将数据从内核拷贝到用户态之后,拿到数据使用

同步非阻塞

  • 第一阶段,无论内核有没有将数据准备好,用户态只要发出read请求后,就会立即返回,然后监听内核数据是否已经准备好,此时不是阻塞状态
  • 第二阶段,用户态需要等待内核态把数据拷贝到用户态,才能去使用数据,这段时间仍然是处于等待状态

IO多路复用(IO多路复用基础是select, poll, epoll)

  • IO多路复用和同步阻塞是一样的,因为它的两个阶段同样也是阻塞的
  • IO多路复用可以通过一个线程同时监听多个描述符,只要有一个满足就绪条件,那么内核态就返回
  • IO多路复用可以使得一个线程同时处理多个IO请求,这就是所谓的Reactor模式

异步IO

  • 异步模式下,两个阶段都不等待
  • 用户态调用read方法后,相当于告诉内核数据:”内核数据发送完成之后通知我一下”,然后自己在这段时间可以完成别的工作,等到内核数据通知数据准备好后正常使用即可,一般会使用callback函数,当数据可用之后执行callback函数

select

函数签名如下:

int select(int maxfdp1, fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

maxfdp1为指定的待监听的描述符的个数,因为描述符是从0开始的,所以需要加1
readset为要监听的读描述符
writeset为要监听的写描述符
exceptset为要监听的异常描述符
timeout监听没有准备好的描述符的话,多久可以返回,支持按照秒或者毫秒来配置时间

select操作的逻辑是首先将要监听的读、写以及异常描述符拷贝到内核空间,然后遍历所有的描述符,如果有感兴趣的事件发生,那么就返回。
select在使用的过程中有三个问题:
1、被监控的fds(描述符)集合限制为1024,1024太小了
2、需要将描述符集合从用户空间拷贝到内核空间
3、当有描述符可操作的时候都需要遍历一下整个描述符集合才能知道哪个是可操作的,效率很低。

poll

函数签名如下:

int poll(struct pollfd[] fds, unsigned int nfds, int timeout);

poll操作与select操作类似,仍旧避免不了描述符从用户空间拷贝到内核空间,但是poll不再有1024个描述符的限制。对于事件的触发通知还是使用遍历所有描述符的方式,因此在大量连接的情况下也存在遍历低效的问题。poll函数在传递参数的时候统一的将要监听的描述符和事件封装在了pollfd结构体数组中。

epoll

epoll有三个方法:epoll_create、epoll_ctl,epoll_wait, epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。 通过这三个方法epoll解决了select的三个问题。
1、1024数量限制的问题
通过epoll_create方法来创建一个epoll句柄,这个句柄监听的描述符的数量不再有限制。

2、文件描述符频繁从用户空间拷贝到内核空间的问题
通过观察select的操作会发现描述符从用户空间到内核空间拷贝发生在调用select方法的时候,只要没有注册新的事件或者取消注册事件,每次拷贝的描述符都是一样的。因此epoll引入了epoll_ctl调用,该方法用于注册新事件和取消注册事件。而在epoll_wait的时候并不会拷贝描述符,描述符始终存在于内核空间,当需要修改的时候只要调用epoll_ctl修改一下内核的描述符即可。如此一来便省去了描述符来回拷贝的开销。

3、文件描述符可操作的时候遍历整个描述符集合的问题
在调用epoll_ctl注册感兴趣的事件的时候,实际上会为设置的事件添加一个回调函数,当对应的感兴趣的事件发生的时候,回调函数就会触发,然后将自己加到一个链表中。epoll_wait函数的作用就是去查看这个链表中有没有已经准备就绪的事件,如果有的话就通知应用程序处理,如此操作epoll_wait只需要遍历就绪的事件描述符即可。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

HashMap Next