Reactor线程模式
一、5种IO模型
在《Unix网络编程》6.2节中提到了unix下可用的五种IO模型
- 阻塞IO
- 非阻塞IO
- 多路复用IO(select和poll)
- 信号驱动IO(SIGIO)
- 异步IO(POSIX的aio_系列函数)
1.阻塞IO模型
最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态。
典型的阻塞IO模型的例子为:
#如果数据没有就绪,就会一直阻塞在read方法。
socket.read();
2.非阻塞IO
应用进程持续调用(轮询,polling)revfrom,来查看某个操作是否就绪。这往往会造成大量CPU时间浪费。
3.IO多路复用
4.信号驱动IO
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个SIGIO信号给用户线程,用户线程接收到信号之后,就可以在信号处理函数中调用IO读写操作来进行实际的IO请求操作。
5.异步IO
异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而从内核的角度,当它受到一个asynchronous read之后,会立刻返回,表明read请求已经成功发起了,因此不会对用户线程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。
注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。
五种IO模型的比较
下图比较了5种不同的IO模型,可以看出,前4种的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于recvfrom调用。相反,异步IO模型在这两个阶段都要处理,从而不同于其它4种模型。
前四种IO模型实际上都不是异步的IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动式IO,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
Java的NIO是属于同步非阻塞IO,关于IO多路复用,Java没有相应的IO模型,但有相应的编程模式,Reactor 就是基于NIO实现多路复用的一种模式。Reactor 模式本质上指的是使用 I/O 多路复用(I/O multiplexing) + 非阻塞 I/O(non-blocking I/O) 的模式。
二、Reactor模型
Java大神Doug Lea在“Scalable IO in Java”中进行了归纳。基于Reactor Pattern 处理模式中,定义以下三种角色:
-
Reactor 将I/O事件分派给对应的Handler
-
Acceptor 处理客户端新连接,并分派请求到处理器链中
-
Handlers 执行非阻塞读/写 任务
传统的BIO模型
1.Reactor单线程模型
2.Reactor多线程模型
3.Reactor主从模型
第三种模型比起第二种模型,是将Reactor分成两部分:mainReactor和subReactor。
mainReactor负责监听server socket,接收(accept)新连接,将建立的socketChannel分派给subReactor。
subReactor维护自己的selector, 基于mainReactor 注册的socketChannel多路分离IO读写事件,读写网络数据,对业务处理的功能,其扔给worker线程池来完成。通常,subReactor的个数可等于CPU的个数。
参考资料: