python关于IO模型

1.事件驱动编程
(1)定义:传统的计算机程序执行都是线性的, 也即程序的执行顺序,行为都是可预测的, 而事件驱动的程序的运行与外部事件有关, 外部输入什么操作,程序就执行相应的函数,完全由事件驱动,事前不可预测;
(2)执行流程:事件驱动程序的一个基本结构是一个事件收集器, 一个事件发送器, 一个事件处理器;
1.有一个事件队列, 其中每个事件对应一个动作;
2.用户发生一个操作, 就往事件队列中加入一个事件;
3.有一个循环, 不断从队列中获取事件, 然后根据不同事件分配给不同函数;
4.事件一般会保存各自处理函数的指针

注意:当队列中没有事件时, 会一直等待(阻塞), 可选择性的释放cpu.
事件驱动的监听事件是由操作系统调用的cpu来完成的

事件驱动的情况下实现IO自动切换, 即是下面说到的IO多路复用.

2.几种IO模型
(1)阻塞IO(blocking IO)
默认的socket编程即使阻塞型的, 特点是等待数据和拷贝数据到用户空间 的阶段都是阻塞的. 下图为读操作的流程图:

在这里插入图片描述
用户程序调用recvfrom, recvfrom向内核发出系统调用, 然后内核直到数据拷贝到用户内存后才返回结果给用户, 用户进程便可直接调取,整个过程一直阻塞

缺点: 程序一直阻塞, 无法执行后续操作;

这样要想实现并发, 可以使用多线程, 多进程, 或线程池等方式.
而多线程一旦连接过多,线程过多,会极大消耗系统资源;

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

recvfrom发起系统调用后, 如果数据未准备好, 内核也会立即返回, 这样用户程序可以不用阻塞继续执行后面的操作, 但需要循环向内核发出系统调用来查询数据是否准备好, 一旦数据准备好,recvfrom便会发起系统调用从内核空间拷贝数据,拷贝数据的过程是阻塞的;
在这里插入图片描述
缺点:大量进行系统调用, 会极大消耗cpu资源; 同时由于查询间隔, 将不能及时的获取数据;

可通过循环调用recv的方式实现多连接,一般很少用这种方案;
示例:

import socket
import time


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8000))
s.listen(5)
s.setblocking(False)  # 非阻塞套接字

while True:
    try:
        con, addr = s.accept()
        while True:
            try:
                data = con.recv(1024)
                print(data.decode('utf8'))
                msg = input('发送:').encode('utf8')
                con.send(msg)
            except BlockingIOError as e1:
                time.sleep(2)
                continue
            except ConnectionResetError:
                break
    except BlockingIOError as e:
        time.sleep(2)  # 无限轮询
#client
import socket

c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('127.0.0.1', 8000))
while True:
    msg = input('发送:')
    c.send(msg.encode('utf8'))
    data = c.recv(1024)
    print(data.decode('utf8'))

(3)多路复用IO(multiplexing IO)

这种方式通过select(poll,epoll)发出系统调用, 如果数据未准备好,程序会一直阻塞,直到数据准备好,内核返回结果, 然后recv就可发出系统调用,从内核空间拷贝数据, 此过程也是阻塞的
在这里插入图片描述

优点:虽然与阻塞IO相比还多一次系统调用, 但select(epoll, poll)可实现同时对多个连接的监听,在单线程实现并发;
缺点:一旦事件相应的执行函数太庞大, 将会严重影响select的监听, 下一个事件的响应迟迟得不到执行;(解决方案:高效的事件驱动库可以屏蔽上述的困难,常见的事件驱动库有libevent库,还有作为libevent替代者的libev库)

epoll的优点: select最多只能监听1024个对象, 而epoll几乎没有限制;另外select探测事件是去轮询每个文件描述符, 比较耗时, 而epoll是只取出有变化的事件; 另外select需要在用户内存和内核内存间传递文件描述符数组, 会消耗内存空间;poll相比select则避开了最多监听对象的限制;

实例1: select 多路复用实现并发

import socket
import select


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8000))
s.listen(5)
s.setblocking(False)
r_list = [s]
while True:
    r, w, e = select.select(r_list, [], [])
    for i in r:
        if i == s:
            con, addr = i.accept()
            print(addr, '连接成功')
            r_list.append(con)

        else:
            data = i.recv(1024)
            print(data.decode('utf8'))
            msg = input('发送:')
            i.send(msg.encode('utf8'))
#client
import socket

c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('127.0.0.1', 8000))
while True:
    msg = input('发送:')
    c.send(msg.encode('utf8'))
    data = c.recv(1024)
    print(data.decode('utf8'))

(4)异步IO(asynchrounous IO)

异步IO整个过程没有阻塞, aio_read发出系统调用后内核立即返回, 不影响程序的执行, 然后直到内核将数据拷贝到用户内存,才通知用户进程
在这里插入图片描述

(5)信号驱动IO(signal driven IO)
在这里插入图片描述

(6)几种模型的比较
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值