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

学习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 select
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)
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,是不是很强大呢?!
待续~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值