Python3之阻塞I/O,非阻塞I/O及I/O多路复用

来源:互联网 发布:金钻淘宝店有哪些 编辑:程序博客网 时间:2024/05/20 18:56

学习I/O模型,主要实现单线程条件下遇到I/O如何解决效率提升问题,也是协程要处理的问题。

阻塞I/O

我们在平时进行的socket编程时,链路循环中服务端接收链接,以及通信循环中服务端接收数据时,就是典型的阻塞I/O模型(主要服务端)。

代码示例

from socket import *s = socket(AF_INET, SOCK_STREAM)s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)s.bind(('127.0.0.1', 8080))s.listen(5)while 1:    conn, addr = s.accept()    while 1:        try:            data = conn.recv(1024)            if not data:break            conn.send(data.upper())        except Exception:            break    conn.close()s.close()

代码解析

  1. 主要两个地方处于阻塞状态:
    1. s.accept():该状态下server套接字等待操作系统把conn连接发给它,但是 操作系统一直在等待客户端链接,这个阶段处于阻塞状态。
    2. data = conn.recv(1024):该状态下conn套接字等待操作系统把数据拷贝给它,操作系统自己也需要等待客户端发送数据过来,这也使整个程序处于阻塞状态。
  2. 先贴张图:
    阻塞I/O

  3. IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段,记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况:

    1. 等待数据准备 (Waiting for the data to be ready)
    2. 将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

非阻塞I/O

非阻塞I/O指在应用程序在操作系统wait for data阶段不需要等待,发一个系统调用给操作系统询问操作系统是否有数据,没有则会去干别的事情,有的话就接收一下数据。

代码示例

from socket import *s = socket(AF_INET, SOCK_STREAM)s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)s.setblocking(False)s.bind(('127.0.0.1', 8080))s.listen(5)conn_l = []del_conn = []while 1:    try:        conn, addr = s.accept()        conn_l.append(conn)    except Exception:        for conn in conn_l:            try:                data = conn.recv(1024)                conn.send(data.upper())            except BlockingIOError:                pass            except ConnectionResetError:        #将客户端断开的链接剔除        for conn in del_conn:            conn.close()            del_conn.remove(conn)        del_conn = [] 

代码解读

  1. 设置s.setblocking(False),套接字为非阻塞状态,这样server套接字不会等操作系统有数据才继续执行。
  2. 没有链接数据会抛一个异常,让程序继续向下执行,走的是通讯循环的逻辑。如果有数据则会将conn加到列表里面进行链接保存。
  3. 此种方式占用CPU过多,不推荐使用

I/O多路复用

代码示例

from select import selectfrom socket import *s = socket(AF_INET, SOCK_STREAM)s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)s.bind(('127.0.0.1', 8080))s.listen(5)select_l = [s, ]while 1:    selected_l, _, _ = select(select_l, [], [])    print(selected_l)    for selected_item in selected_l:        if selected_item == s:            conn, addr = selected_item.accept()            select_l.append(conn)        else:            data = selected_item.recv(1024)            selected_item.send(data.upper())

注意问题

  1. select模块会自动过滤没有数据的套接字,有的话会将该套接字放入子集里面。
  2. 根据加入的套接字进行判断,是server套接字就取出链接和地址,是conn套接字就接收发送数据。
  3. select模块工作是基于列表去筛选哪个套接字有数据,效率较低,而且有套接字监控数量的限制,而poll只是对套接字监控数量做了扩展,没有实现高效筛选套接字的机制。

实现I/O多路复用的机制

select、poll、epoll
其中epoll克服了select和poll筛选效率低的问题,套接字数据有了就主动通知调用者进行处理,通过回调函数机制实现效率的大幅提升。但是只在linux平台下可以使用。这样selectors模块应运而生!它可以根据用户使用不同的平台来自动选择该使用select还是epoll,是不是很强大呢?!
待续~

原创粉丝点击