一、并发、并行、同步、异步、阻塞、非阻塞
并发:指一个时间段内,有几个程序在同一个CPU上运行,但是任意时刻只有一个程序在CPU上运行
并行:指任意时刻点上,有多个程序同时运行在多个CPU上
同步:指代码调用IO操作时,必须等待IO操作完成才能返回的调用方式。
异步:指代码调用IO操作时,不必等IO操作完成就返回的调用方式。
阻塞:指调用函数时候当前线程被挂起。
非阻塞:值调用函数时当前线程不会被挂起,而是立即返回。
阻塞非阻塞是对调用者而言,调用结果返回前进程的状态,是挂起还是继续处理其他任务。
同步异步是对被调用者,结果返回时通知进程的一种通知机制。
二、阻塞I/O(BIO)
BIO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
解决方案:
在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
问题:开启多进程或多线程的方式,在遇到要同时响应成百上千路的连接请求,会严重占据系统资源,降低系统对外界响应效率。
改进:使用“线程池”或“连接池”。
“线程池”旨在减少创建和销毁线程的频率,使其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。
“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。
这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。
改进后方案的问题:
“线程池”和“连接池”也只是在一定程度上缓解了频繁调用I/O接口带来的资源占用。而且,“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
三、非阻塞I/O(NIO)
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的询问,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。
在NIO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
四、多路复用I/O(IO multiplexing)
一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力。select的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
select、poll、epoll的区别
1、select==>时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流,我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
2、poll==>时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。
3、epoll==>时间复杂度O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的。
select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。