先来说说四个概念:
- 同步
同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。 - 异步
异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 - 阻塞
阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 - 非阻塞
非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
是不是还有点模糊,通俗的话讲就是:
这里只考虑两个实体(客户端、服务端)
一个事件(客户端向服务端请求数据)
同步和异步是针对于服务端来讲
- 同步 客户端找服务器 (客户端轮询的查询服务器,看是否有结果返回)
- 异步 服务器找客户端 (服务器有数据返回时,通过回调等机制通知客户端有数据返回)
阻塞和非阻塞是针对于客户端来讲的
- 阻塞: 客户端发起请求时,服务端不给结果,客户端线程挂起,不做其他事情
- 非阻塞: 客户端发起请求时,服务端不给结果,客户端可以先去干其他的事情
还没明白?你再品:
举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。
你就是客户端 水壶 就是服务端
BIO 同步阻塞
- 流程解析: 用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。
- 同步阻塞,当没有数据返回的时候,当前线程处于等待状态
同步非阻塞IO
- 流程解析: 由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。
- 相比于BIO 用户线程请求内核后立马收到返回,用户线程为非阻塞状态,可以执行别的操作,但是需要不停的轮询调用内核,查看数据是否准备完成,比较耗费CPU性能
多路复用 NIO
形成原因: 如果一个I/O流进来,我们就开启一个进程处理这个I/O流。那么假设现在有一百万个I/O流进来,那我们就需要开启一百万个进程一一对应处理这些I/O流(——这就是传统意义下的多进程并发处理)。思考一下,一百万个进程,你的CPU占有率会多高,这个实现方式及其的不合理。所以人们提出了I/O多路复用这个模型,一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力。
- 当进程调用select,进程就会被阻塞
- 此时内核会监视所有select负责的的socket,当socket的数据准备好后,就立即返回。
- 进程再调用read操作,数据就会从内核拷贝到进程。
其实多路复用的实现有多种方式:select、poll、epoll
select的基本原理:
- 监视文件3类描述符: writefds、readfds、和exceptfds;
- 调用后select函数会阻塞住,等有数据 可读、可写、出异常 或者 超时 就会返回
- select函数正常返回后,通过遍历fdset整个数组才能发现哪些句柄发生了事件,来找到就绪的描述符fd,然后进行对应的IO操作
- 几乎在所有的平台上支持,跨平台支持性好
缺点:
- select采用轮询的方式扫描文件描述符,全部扫描,随着文件描述符FD数量增多而性能下降
- 每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)
- 最大的缺陷就是单个进程打开的FD有限制,默认是1024 (可修改宏定义,但是效率仍然慢)
- static final int MAX_FD = 1024
poll的基本流程:
- select() 和 poll() 系统调用的大体一样,处理多个描述符也是使用轮询的方式,根据描述符的状态进行处理
- 一样需要把 fd 集合从用户态拷贝到内核态,并进行遍历。
- 最大区别是: poll没有最大文件描述符限制(使用链表的方式存储fd)
epoll基本原理:
在2.6内核中提出的,对比select和poll,epoll更加灵活,没有描述符限制,用户态拷贝到内核态只需要一次使用事件通知,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用callback的回调机制来激活对应的fd
优点:
- 没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
- 效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
- 通过callback机制通知,内核和用户空间mmap同一块内存实现(减少了数据复制)
句柄是什么?
句柄就是一个标识符,只要获得对象的句柄,我们就可以对对象进行任意的操作。
句柄不是指针,操作系统用句柄可以找到一块内存,这个句柄可能是标识符,map的key,也可能是指针,看操作系统怎么处理的了。fd算是在某种程度上替代句柄吧;Linux 有相应机制,但没有统一的句柄类型,各种类型的系统资源由各自的类型来标识,由各自的接口操作。
在操作系统层面上,文件操作也有类似于FILE的一个概念,在Linux里,这叫做文件描述符(File Descriptor),而在Windows里,叫做句柄(Handle)(以下在没有歧义的时候统称为句柄)。用户通过某个函数打开文件以获得句柄,此后用户操纵文件皆通过该句柄进行。
参考原文链接:
https://blog.csdn.net/zhuyanlin09/article/details/102730344
https://blog.csdn.net/weililansehudiefei/article/details/70885515