IO阻塞与非阻塞
同步:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步:
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
对象的阻塞模式和阻塞函数调用
对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状 态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。
IO多路转接
它也是会阻塞,不过它是可以阻塞多个IO,实现近似于非阻塞IO,先构造一张有关描述符的数据表,然后调用一个函数,仅当有一个或多个描述符已准备可以进行IO操作时才返回,否则一直阻塞。在返回时,它会告诉进程那些描述符已准备好可以进行IO。
select函数的参数将告诉内核:
(1) 我们所关心的描述符。
(2) 对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的
描述符?是否关心一个描述符的异常条件?)。
(3) 希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)
select从内核返回后内核会告诉我们:
(1) 已准备好的描述符的数量。
(2) 哪一个描述符已准备好读、写或异常条件。
select 用于查询设备的状态,以便用户程序获知是否能对设备进行非阻塞的访问,需要设备驱动程序中的poll 函数支持。 驱动程序中 poll 函数中最主要用到的一个 API 是 poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait 函数所做的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中。
需要说明的是,poll_wait 函数并不阻塞,程序中 poll_wait(filp, &outq, wait)这句话的意思并不是说一直等待 outq 信号量可获得,真正的阻塞动作是上层的 select/poll 函数中完成的。select/poll 会在一个循环中对每个需要监听的设备调用它们自己的 poll 支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪,则内核进行调度(调用 schedule)让出 cpu 进入阻塞状态,schedule 返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪,select/poll 都立即返回。
内核中实现select是用轮询方法
epoll
1>支持一个进程打开大数目的socket描述符(FD)
2>IO效率不随FD数目增加而线性下降,它的内核实现只会对"活跃"的socket进行 操作,而不是全部轮询
3>使用mmap加速内核与用户空间的消息传递。epoll是通过内核于用户空间mmap同一块内存实现来进行消息通知的。
Nginx并发策略
ginx使用的是一个进程处理多个连接、非阻塞IO模式 ,这种模式最特别的是设计了独立的listener进程,专门负责接收新的连接,再分配给各个worker,当然为了减少任务调度的开销,一般都是由worker进程来进行接收。在listener进程中使用epoll来进行非阻塞IO。
信号驱动IO
简介:两次调用,两次返回;
首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
异步IO模型
简介:数据拷贝的时候进程无需阻塞。
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作
5个I/O模型的比较:
java NIO
nio 是java nonblocking(非阻塞) IO 的简称
*为所有的原始类型提供(Buffer)缓存支持。
*字符集编码解码解决方案。
*Channel :一个新的原始I/O 抽象。
*支持锁和内存映射文件的文件访问接口。
*提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。
1.缓冲Buffer
2.通道
NIO Socket编程中有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的套接字通道socketchannel注册到Selector,程序不用阻塞等待,可以并行做别的事情,当有事件发生时,Selector会通知程序,传回一组SelectionKey,程序读取这些Key,就会获得注册过的socketchannel,然后,从这个Channel中读取和处理数据。
Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。
eg:
- import java.net.*;
- import java.util.*;
- import java.io.*;
- import java.nio.*;
- import java.nio.channels.*;
- import java.nio.charset.*;
- public class NIOSocketServer{
- public static final int PORT = 8080;
- public static void main(String[] args)throws IOException{
- //NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,
- //对于以字符方式读取和处理的数据必须要进行字符集编码和解码
- String encoding = System.getProperty(“file.encoding”);
- //加载字节编码集
- Charset cs = Charset.forName(encoding);
- //分配两个字节大小的字节缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(16);
- SocketChannel ch = null;
- //打开服务端的套接字通道
- ServerSocketChannel ssc = ServerSocketChannel.open();
- //打开通道选择器
- Selector sel = Selector.open();
- try{
- //将服务端套接字通道连接方式调整为非阻塞模式
- ssc.configureBlocking(false);
- //将服务端套接字通道绑定到本机服务端端口
- ssc.socket().bind(new InetSocketAddress(PORT));
- //将服务端套接字通道OP_ACCEP事件注册到通道选择器上
- SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT);
- System.out.println(“Server on port:” + PORT);
- while(true){
- //通道选择器开始轮询通道事件
- sel.select();
- Iterator it = sel.selectedKeys().iterator();
- While(it.hasNext()){
- //获取通道选择器事件键
- SelectionKey skey = (SelectionKey)it.next();
- it.remove();
- //服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接
- if(skey.isAcceptable()){
- //获取服务端套接字通道上连接的客户端套接字通道
- ch = ssc.accept();
- System.out.println(“Accepted connection from:” + ch.socket());
- //将客户端套接字通过连接模式调整为非阻塞模式
- ch.configureBlocking(false);
- //将客户端套接字通道OP_READ事件注册到通道选择器上
- ch.register(sel, SelectionKey.OP_READ);
- }
- //客户端套接字通道已经连接
- else{
- //获取创建此通道选择器事件键的套接字通道
- ch = (SocketChannel)skey.channel();
- //将客户端套接字通道数据读取到字节缓冲区中
- ch.read(buffer);
- //使用字符集解码字节缓冲区数据
- CharBuffer cb = cs.decode((ByteBuffer)buffer.flip());
- String response = cb.toString();
- System.out.println(“Echoing:” + response) ;
- //重绕字节缓冲区,继续读取客户端套接字通道数据
- ch.write((ByteBuffer)buffer.rewind());
- if(response.indexOf(“END”) != -1) ch.close();
- buffer.clear();
- }
- }
- }
- }finally{
- if(ch != null) ch.close();
- ssc.close();
- sel.close();
- }
- }
- }
java NIO 和阻塞I/O的区别
1. 阻塞I/O通信模型
假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞(线程让出CPU)到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:
如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,两点缺点:
1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间
2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。
这种情况下非阻塞式I/O就有了它的应用前景。
2. java NIO原理及通信模型
Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:
1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:
(注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)
Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
事件名 | 对应值 |
服务端接收客户端连接事件 | SelectionKey.OP_ACCEPT(16) |
客户端连接服务端事件 | SelectionKey.OP_CONNECT(8) |
读事件 | SelectionKey.OP_READ(1) |
写事件 | SelectionKey.OP_WRITE(4) |
服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的java NIO的通信模型示意图:
参考:http://blog.csdn.net/hguisu/article/details/7453390
http://www.cnblogs.com/zhuyp1015/archive/2012/05/31/2529079.html