python IO模型:阻塞、非阻塞、io多路复用、异步

大前提:

# 前提需知道:操作系统的内核态和用户态 ,通信将数据缓存到内核,然后操作系统执行accept到用户态

# IO发生时涉及的对象:
# 对于一个network IO (这里我们以read举例),它会涉及到两个系统对象:
# 一个是调用这个IO的进程或者线程
# 另一个就是系统内核(kernel)
#
#
# IO发生时涉及的步骤:
# 当一个read操作发生时,它会经历两个阶段:
#  1 等待数据准备 (Waiting for the data to be ready)
#  2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
# 记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

# 在linux中,默认情况下所有的socket都是blocking,即阻塞IO。

1、blocking IO (阻塞IO):

当用户进程调用了recvfrom这个系统调用,内核kernel就开始了IO的第一个阶段:准备数据。
对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),
这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。

当内核kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,
用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

简单总结:数据没来,一直等,没数据,复制也不行,一直等

2、non-blocking IO(非阻塞IO)

linux下,可以通过设置socket使其变为non-blocking。

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。
用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。
所以,用户进程其实是需要不断的主动询问kernel数据好了没有。进程在返回之后,可以干点别的事情,然后继续轮询。
注意,复制数据阶段仍然处于阻塞阶段。


3、IO multiplexing(IO多路复用)

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

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

用select的优势在于它可以同时处理多个connection。
所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定
比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。
select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

注意1:select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()
来让kernel将位于内核中准备到的数据copy到用户区。

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

案例代码:
# IO多路复用server 端

import socket

import select
sock=socket.socket()
sock.bind(("127.0.0.1",8888))

sock.listen(5)

sock.setblocking(False)
inputs=[sock,]

while 1:

    r,w,e=select.select(inputs,[],[])

    for obj in r:
        if obj == sock:
            conn,addr=obj.accept()
            print("conn",conn)
            inputs.append(conn)
        else:
            data = obj.recv(1024)
            print(data.decode("utf8"))

            msg=input(">>>:")
            obj.send(msg.encode("utf8"))
# IO多路复用client 端

import socket

sock=socket.socket()
sock.connect(("127.0.0.1",8888))

while 1:
    msg=input(">>:")
    sock.send(msg.encode("utf8"))
    data=sock.recv(1024)
    print(data.decode("utf8"))

sock.close()

 




4、AsynchronousI / O(异步IO):

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



小总结:
# 阻塞IO:等数据,数据不来,不走,全程阻塞
# 一次accept,全程阻塞


# 非阻塞io:问你有没有数据,有带着数据走,没有,带着错误走
# socket.socket().setblocking(False)
# 缺点:发多次系统调用,拿到的数据不一定是实时的
# 优点:等待数据时无阻塞
# 两个阶段:
# 等待数据非阻塞
# 复制数据阻塞



# IO多路复用:(重点)
# conn没有新链接监听
# select监听有变化的套接字,有新链接
# select监听数据,数据到了告诉他,不到就阻塞

# 套接字对象(文件描述符):
# 是一个非零整数,不会变
# 收发数据的时候,对于接收端而言,数据先到内核空间,然后 copy
# 到用户空间,同时,内核空间数据清除。

# 2次调用,全程阻塞,select一次,accept一次
# 特点:
# 1全程阻塞(wait for data,copy)
# 2能监听多个文件描述符
# 3实现并发

# 异步IO:
# linux 下用的很少,实现复杂,用的少

# 有一个阻塞就是同步,
# 同步:阻塞io,非阻塞io,io多路复用
# 异步:异步io

 



posted @ 2017-05-10 15:44  Adamanter  阅读(184)  评论(0编辑  收藏  举报