Vernacular five kinds of IO models

A, I / O model described in

To better understand the I / O model, we need to review in advance at: synchronous, asynchronous, blocking, non-blocking

Synchronous (synchronous) I / O and asynchronous (asynchronous) I / O, blocking (blocking) (non-blocking) I / O and what is non-blocking I / O, respectively, in the end what is the difference? In fact, the answer to this question given by different people may be different, such as wiki, considered asynchronous I / O and non-blocking I / O is a thing. In fact, this is because different people of different background knowledge, and when discussing the issue of context (context) is not the same. Therefore, in order to better answer this question, let me define what the context of this article.

Background This article discusses the network I / O in a Linux environment. The most important references herein is Richard Stevens of "UNIX® Network Programming Volume 1, Third EditI / On: The Sockets Networking", Section 6.2 "I / O Models", Stevens In this section details the various I / O the characteristics and differences, if English is good enough to recommend direct reading. Stevens' style is well-known in layman's language, so do not worry do not understand. Flowchart herein also taken from reference.

Stevens in the article comparing the cost of five kinds of the I / O Model :

English Chinese
blocking I/O Blocking I / O
nonblocking I/O Non-blocking I / O
I/O multiplexing I / O multiplexer
signal driven I/O Drive signal I / O
asynchronous I/O 异步 I / O

To tell you the object and the steps involved in I / O occur. For a Network I / O (here we read for example), it would involve two system objects, it is a call to the I / O of process (or thread), and the other is system kernel (kernel). When a read operation occurs, the operation will go through two stages:

  1. Wait for data preparation (Waiting for the data to be ready)
  2. Copying the data from the kernel to process (Copying the data from the kernel to the process)

Remember these two points is important because the difference between these I / O model is to have different situations in two stages.

In the network environment, then popular speaking, the I / O is divided into two steps:

  1. Wait;
  2. Data relocation.

If you want to improve I / O efficiency, such as the need to reduce the time.

Five kinds of I / O model comprising: blocking I / O, non-blocking I / O, the drive signal I / O, I / O multiplexer, asynchronous I / O. Wherein, the first four are called a synchronous I / O.

Introducing the five kinds of I / O model, I will give an example of life in the Pharaoh to buy tickets, to deepen understanding.

Second, blocking I / O model

For example buying of example, the summary of the model:

# 老王去火车站买票,排队三天买到一张退票。

# 耗费:在车站吃喝拉撒睡 3天,其他事一件没干。

In linux, by default, all socket is blocking, a typical read operation process something like this:

188 vernacular five kinds IO model -01.png? X-oss-process = style / watermark

When the user process calls the recvfrom system call, kernel began the first phase of I / O: data ready. For the network I / O, the data is often not yet arrived at the beginning (for example, has not yet received a complete UDP packet), this time the kernel will wait for enough data to come.

In the process the user side, the whole process will be blocked. When the kernel wait until the data is ready, it will copy data from kernel to user memory, and then returns the result kernel, the user process before lifting the state of the block, up and running again. Therefore, blocking I / O feature is the I / O execution of the two phases (two wait for data and copy data phases) are a block.

Almost all programmers to first contact the network are programmed from the listen (), send (), recv () starts interfaces can be constructed model server / client easily use these interfaces. However, most of the socket interface are blocking. As shown below

ps: the so-called obstructive interface refers to the system call (usually I / O interface) does not return a result of the call and let the current thread has been blocked only when the system call or return to get the results when the time-out error.

188 vernacular five kinds IO model -02.png? X-oss-process = style / watermark

In fact, unless otherwise specified, almost all of the I / O interfaces (including socket interfaces) are blocking the. This gives network programming has brought a lot of problems, such as at the same time calling recv (1024), the thread will be blocked during this time, the thread will not be able to perform any operation or respond to any network requests.

2.1 a simple solution

On the server side using multiple threads (or processes). The purpose multi-thread (or process) is to allow each connection has a separate thread (or process), so any blocking a connection will not affect other connections.

2.2 The program issue

Turn on multiple processes or threads all the way in the face while responding to a connection request to hundreds of the road, regardless of multi-threaded or multi-process would seriously occupy system resources and reduce system efficiency in response to the outside world, but also threads and processes themselves easier access to state of suspended animation.

2.3 improvement program

Many programmers might consider using "thread pool" or "connection pooling." "Thread pool" to reduce the frequency of thread creation and destruction, which maintains a reasonable number of threads, and let idle thread again take on new tasks. "Connection Pool" to maintain the buffer pool connection, try to reuse existing connections, reducing the frequency of creating and close connections. Both techniques can be very good to reduce system overhead, it has been widely used in many large systems, such as websphere, tomcat and a variety of databases.

2.4 improvement program after problem

“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用I/O接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

三、非阻塞I/O模型

以买票的例子举例,该模型小结为

# 老王去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。

# 耗费:往返车站6次,路上6小时,其他时间做了好多事。

Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

188 vernacular five kinds IO model -03.png? X-oss-process = style / watermark

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。所以,在非阻塞式I/O中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

3.1 非阻塞I/O实例

#服务端
from socket import *
import time
s=socket(AF_INET,SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
conn_l=[]
del_l=[]
while True:
    try:
        conn,addr=s.accept()
        conn_l.append(conn)
    except BlockingI/OError:
        print(conn_l)
        for conn in conn_l:
            try:
                data=conn.recv(1024)
                if not data:
                    del_l.append(conn)
                    continue
                conn.send(data.upper())
            except BlockingI/OError:
                pass
            except ConnectI/OnResetError:
                del_l.append(conn)

        for conn in del_l:
            conn_l.remove(conn)
            conn.close()
        del_l=[]

#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8080))

while True:
    msg=input('>>: ')
    if not msg:continue
    c.send(msg.encode('utf-8'))
    data=c.recv(1024)
    print(data.decode('utf-8'))

但是非阻塞I/O模型绝不被推荐。

我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。

但是也难掩其缺点:

  1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况
  2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

此外,在这个方案中recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如select()多路复用模式,可以一次检测多个连接是否活跃。

四、多路复用I/O模型

4.1 select/poll模型

以买票的例子举例,select/poll模型小结为:

1.
# 老王去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老王去火车站交钱领票。

# 耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次

I/O multiplexing这个词可能有点陌生,但是如果我说select/poll,大概就都能明白了。有些地方也称这种I/O方式为事件驱动I/O(event driven I/O)。我们都知道,select/poll的好处就在于单个process就可以同时处理多个网络连接的I/O。它的基本原理就是select/poll这个functI/On会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

188 vernacular five kinds IO model -04.png? X-oss-process = style / watermark

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和blocking I/O的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking I/O只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connectI/On。

强调:

  1. 如果处理的连接数不是很高的话,使用select/poll的web server不一定比使用multi-threading + blocking I/O的web server性能更好,可能延迟还更大。select/poll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
  2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket I/O给block。

    结论: select的优势在于可以处理多个连接,不适用于单个连接

4.1.1 select网络I/O模型

#服务端
from socket import *
import select

s=socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8081))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
read_l=[s,]
while True:
    r_l,w_l,x_l=select.select(read_l,[],[])
    print(r_l)
    for ready_obj in r_l:
        if ready_obj == s:
            conn,addr=ready_obj.accept() #此时的ready_obj等于s
            read_l.append(conn)
        else:
            try:
                data=ready_obj.recv(1024) #此时的ready_obj等于conn
                if not data:
                    ready_obj.close()
                    read_l.remove(ready_obj)
                    continue
                ready_obj.send(data.upper())
            except ConnectI/OnResetError:
                ready_obj.close()
                read_l.remove(ready_obj)

#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))

while True:
    msg=input('>>: ')
    if not msg:continue
    c.send(msg.encode('utf-8'))
    data=c.recv(1024)
    print(data.decode('utf-8'))

4.1.2 select监听fd变化的过程分析

  1. 用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后,就会发送信号给用户进程数据已到;

  2. 用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除,这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。

4.1.3 该模型的优点

相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

4.1.4 该模型的缺点

  1. 首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。
  2. 很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
  3. 如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,
  4. 所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。
  5. 其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。

4.2 epoll模型(了解)

以买票的例子举例,epoll模型小结为:

# 老王去火车站买票,委托黄牛,黄牛买到后即通知老王去领,然后老王去火车站交钱领票。

# 耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话

I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现)。但是,select模型有着一个很大的问题,那就是它所支持的fd的数量是有限制的,从linux源码中所看:

188 vernacular five kinds IO model -08.png? X-oss-process = style / watermark

它所需要的fd_set类型其实是一个__FD_SETSIZE长度bit的数组。也就是说通过FD_SET宏来对它进行操作的时候,需要注意socket不能过大,否则可能出现数组写越界的错误。

而linux在2.6 内核中引入的epoll模型,就彻底解决了这个问题,由于目前epoll模型只能在linux中使用,因此我们开发仅做了解即可。

epoll模型提供了三个接口:

188 vernacular five kinds IO model -09.png? X-oss-process = style / watermark

其使用流程基本与select一致,大致如下:

188 vernacular five kinds IO model -10.png? X-oss-process = style / watermark

epoll_create函数会为要监听的fd分配内存。

epoll_ctl函数将要监听的fd拷贝到内核空间,从而避免每次等待事件都要进行内存拷贝。同时,注册一个回调函数到 fd的设备等待队列中,这样,当设备就绪的时候,驱动程序可以直接调用回调函数进行处理,从而避免了对所有监听fd的轮循。

epoll_wait函数会检查是否已经有fd就绪了,如果有则直接返回,如果没有,则进入休眠状态,直到被上述的回调函数唤醒或者超时时间到达。

五、信号驱动I/O模型(了解)

以买票的例子举例,该模型小结为:

# 老王去火车站买票,给售票员留下电话,有票后,售票员电话通知老王,然后老王去火车站交钱领票。

# 耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话

188 vernacular five kinds IO model -07.png? X-oss-process = style / watermark

由于信号驱动I/O在实际中并不常用,所以我们只做简单了解。

信号驱动I/O模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGI/O信号进行捕捉,并且调用我的信号处理函数来获取数据报。

六、异步I/O模型

以买票的例子举例,该模型小结为:

# 老王去火车站买票,给售票员留下电话,有票后,售票员电话通知老王并快递送票上门。

# 耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话

Linux下的asynchronous I/O其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:

188 vernacular five kinds IO model -05.png? X-oss-process = style / watermark

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

七、I/O模型比较分析

到目前为止,已经将四个 I/O Model都介绍完了。现在回过头来回答最初的那几个问题:阻塞和非阻塞的区别在哪,同步I/O 和 异步I/O 的区别在哪。

先回答最简单的这个:阻塞 vs 非阻塞。前面的介绍中其实已经很明确的说明了这两者的区别。调用 阻塞I/O 会一直block住对应的进程直到操作完成,而 非阻塞I/O 在kernel还准备数据的情况下会立刻返回。

再说明同步I/O 和 异步I/O 的区别之前,需要先给出两者的定义。Stevens给出的定义(其实是POSIX的定义)是这样子的:

A synchronous I/O operatI/O n causes the requesting process to be blocked until that I/O operatI/O ncompletes;

An asynchronous I/O operatI/O n does not cause the requesting process to be blocked;

两者的区别就在于 同步I/O 做”I/O operatI/O n”的时候会将process阻塞。按照这个定义,四个I/O模型可以分为两大类,之前所述的 阻塞I/O , 非阻塞I/O ,I/O多路复用 都属于 同步I/O 这一类,而 异步I/O 属于后一类。

有人可能会说, 非阻塞I/O 并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”I/O operatI/O n”是指真实的I/O操作,就是例子中的recvfrom这个system call。 非阻塞I/O 在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而 异步I/O 则不一样,当进程发起I/O操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说I/O完成。在这整个过程中,进程完全没有被block。

各个I/O Model的比较如图所示:

188 vernacular five kinds IO model -06.png? X-oss-process = style / watermark

After the above description, you will find the difference between non-blocking I / O and asynchronous I / O is still very obvious. In the non-blocking I / O, although most of the time the process will not be block, but it still requires the process to take the initiative in check, and when the data is ready after completion of the process also need to take the initiative to call recvfrom again to copy the data to the user RAM. And asynchronous I / O is completely different. It's like the entire user process I / O operations to the others (kernel) is completed, then signaled the others to finish. During this time, the user process does not need to check the I / O operation status, it does not need to copy data active.

As can be seen, the extent of more than five occlusion model from low to high as: blocking I / O> non-blocking I / O> multiplexer I / O> signal driver I / O> asynchronous I / O, so their efficiency is from low to high.

Guess you like

Origin www.cnblogs.com/nickchen121/p/11145098.html
Recommended