1、针对非阻塞I/O执行的系统调用总是立即返回,而不管事件是否已经发生。如果事件没有立即发生,这些系统调用返回-1,和出错情况是一样的。此时必须根据errno来区分这两种情况。
很显然,我们只能在事件已经发生的情况下操作非阻塞I/O,才能提高程序效率,因此,非阻塞I/O通常需要和其他的I/O通知机制一起使用,比如I/O复用和SIGIO信号。
2、I/O复用是最常使用的I/O通知机制。他指的是,应用程序通过I/O复用函数向内核注册一组事件,内核通过I/O复用函数把其中就绪的事件通知给应用程序。Linux常用的I/O复用函数有select、poll、epoll_wait
3、阻塞I/O、I/O复用和信号驱动I/O都是都是同步I/O模型。因为在这三种I/O模型中,I/O的读写操作都是在I/O事件发生之后,由应用程序来完成的。而异步I/O模型则不同,用户可以直接对I/O执行读写操作,这些操作告诉内核读写缓冲区的位置,以及I/O操作完成之后内核通知应用程序的方式。异步I/O的读写操作总是立即返回,无论I/O是否是阻塞的,因为真正的读写操作已经由内核接管。
也就是说,(1)同步I/O模型要求用户代码自行执行I/O操作(将数据从内核缓冲区读入用户缓冲区,或者将数据从用户缓冲区写入内核缓冲区)
(2)而异步I/O机制则由内核来执行实际的I/O操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在后台完成的)
最后,总结一句话,可以认为,同步I/O向应用程序通知I/O就绪事件,而异步I/O向应用程序通知I/O完成事件。
两种高效的事件处理模型:Reactor模式和Proactor模式。一般情况下,同步I/O用于实现Reactor模式,异步I/O用于实现Proactor模式。
4、两种高效的并发模式:半同步/半异步 和 领带着/追随者
如果程序是计算密集型的,并发编程并没有太大优势,反而由于任务的切换降低效率。但如果程序是I/O密集型的,比如经常读写文件,访问数据库等,则情况就不一样了。由于I/O操作的速度远远没有CPU计算速度快,所以让程序阻塞于I/O操作将浪费大量的CPU时间。如果程序有多个执行线程,在当前被I/O操作所阻塞的执行线程可以主动放弃的CPU(或者由操作系统来调度),并将执行权交给其他的线程,这样一来,CPU就可以用来做更多有意义的事情(除非所有线程都被I/O操作阻塞),而不是等待I/O操作完成。
并发编程主要有多进程和多线程两种。
并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法,服务器主要有两种并发模式:半同步/半异步
和 领带着/追随者
(1)半同步/半异步
这里的“同步”和“异步”与I/O模型中的“同步”和“异步”是完全不同的概念。
a)在I/O模型中,“同步”和“异步”区分的是内核向应用程序通知的是何种I/O事件(就绪事件还是完成事件),以及由谁来真正的执行I/O操作(是应用程序自己完成还是交由内核来完成)
b)在并发模式中,“同步”指的是程序完全按照代码序列顺序执行;“异步”指的是程序的执行需要由系统事件来驱动。常见的有中断、信号等。
如下图:
按同步方式执行的线程称为同步线程,按异步方式执行的线程称为异步线程。显然异步线程的执行效率高。但是编写异步执行的程序相对复杂,,难于调试和扩展,而且不太适应于大量的并发。而同步线程则相反,虽然效率低,实时性较差,但是逻辑简单。因此对于服务器这种既要求较好的实时性,又要求同时能处理多个客户的请求的应用程序,我们就应该同时使用同步线程和异步线程,即半同步/半异步模式。
在半同步/半异步模式中,同步线程用于处理客户逻辑,异步线程用于处理I/O事件。异步线程监听到客户请求后,将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理请求对象。
结合考虑两种事件处理模式和集中I/O模型,则半同步/半异步模式模式存在多种变体,其中最主要的是半同步/半反应堆模式(half-sync/half-reactive)
半同步/半反应堆模式(half-sync/half-reactive):
半同步/半反应堆模式(half-sync/half-reactive)采用的事件处理模式是Reactor模式:要求工作线程自己从socket上读取客户请求和往socket上写入服务器应答。这就是该模式中名称half-sync/half-reactive的含义。