(Linux网络编程笔记)阻塞非阻塞、同步异步、reactor
、proactor
阻塞与非阻塞
在调用一个系统调用时,该系统调用不会立即返回,而是当还未准备就绪时,会被阻塞,也就是阻塞到有结果再返回。
例如在阻塞socket
中调用recv()
,如果缓冲区中没有数据,则会一直阻塞等待有数据为止。
而非阻塞调用则会立即返回,如果该调用的数据未准备好,则会返回错误,如EWOULDBLOCK
。假设调用非阻塞socket
的recvfrom()
,此时则会立即返回,若无数据,返回EWOULDBLOCK
。此时需要多次调用recvfrom()
。
即阻塞非阻塞都是针对于发起操作时,检查是否就绪阶段。
同步、异步
同步异步都是针对于真正发起操作之后的行为。
同步是指要等待I/O操作完毕,当数据就绪后,也就是有数据可读时,要将process阻塞,一直到读完为止。
而对于异步,用户发起操作之后,可以立即去处理其他操作。全程交给内核处理。
即同步异步针对于数据操作阶段,与阻塞非阻塞无关。
I/O Model对比
阻塞I/O
就是最一般的I/O,在读取、写入时要阻塞等待。
不太恰当的例子
A在钓鱼,用的是最原始的鱼竿,必须一直守着等待鱼上钩再拉杆。
非阻塞I/O
我们把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
不太恰当的例子
B在钓鱼,用的是一种相对先进的鱼竿,上面会显示鱼有没有上钩,因此每查看一次就能立即知道是否上钩(对应于调用立即能有结果)。但是要不断循环检查。
I/O复用
典型的有select
、epoll
。
I/O复用本身是阻塞的。也就是说,I/O复用函数调用实际在发起操作时也需要阻塞等待就绪,只不过I/O复用能够处理多个socket。
不太恰当的例子
B用的跟A一样的鱼竿,只是B觉得A太慢了,第一种情况,B买了很多鱼竿,并且雇佣了工人不停地轮询他的鱼竿(select/poll),一旦有上钩,就在鱼竿上做个标记,于是B就需要轮询一边看哪个鱼竿上钩。第二种,就如同在每一根鱼竿上安装了闹钟(epoll),一旦上钩,就会通知B某个鱼竿上钩了。
信号驱动I/O(SIGIO)
首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据
不太恰当的例子
这里也相当于在鱼竿安装了闹钟,但区别于epoll可能在于不能管理多个鱼竿。
以上都属于同步I/O,在数据拷贝时,都会被阻塞。也就是说,鱼上钩之后,拉上来的过程要本人去拉,这可以看作阻塞,因为渔者不能干别的事情。
异步I/O
用户进程发起read操作之后,立刻就可以开始去做其它的事。全程不需要关心,都交给内核完成。
不太恰当的例子
C是个大老板,他只要跟他佣人说,我要钓鱼。剩下的活全由佣人完成,C只需要去拿鱼就行。
Reactor、Proactor
在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。
搞清楚了以上概念以后,我们再回过头来看看,Reactor模式和Proactor模式。
首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。我们以读操作为例来看看Reactor中的具体步骤:
读取操作:
-
应用程序注册读就需事件和相关联的事件处理器
-
事件分离器等待事件的发生
-
当发生读就需事件的时候,事件分离器调用第一步注册的事件处理器
-
事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理
下面我们来看看Proactor模式中读取操作和写入操作的过程:
读取操作:
-
应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
-
事件分离器等待读取操作完成事件
-
在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作,并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
-
事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。
Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。
从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.