IO多路复用


title: IO多路复用
top: 43
date: 2022-07-07 14:06:39
tags:

  • io多路复用
    categories:
  • IO

IO多路复用

能够阻塞等待,非忙轮询装填,不浪费CPU等资源,也能同一时刻监听多个IO请求的机制。

  • IO 多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;
  • 一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;
  • 没有文件句柄就绪就会阻塞应用程序,交出CPU。

多路是指网络连接,复用指的是同一个线程

select机制

客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常 或超时、就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的IO操作。

  • 优点:
    几乎在所有的平台上支持,跨平台支持性好
  • 缺点:
    • 由于是采用轮询方式全盘扫描,会随着文件描述符FD数量增多而性能下降。
    • 每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)
    • 默认单个进程打开的FD有限制是1024个,可修改宏定义,但是效率仍然慢。

poll机制

  • 基本原理与select一致,只是没有最大文件描述符限制,因为采用的是链表存储fd。

epoll机制

  • epoll之所以高性能是得益于它的三个函数

    • epoll_create()系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd
    • epoll_ctl() 每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数。
    • epoll_wait() 轮训所有的callback集合,并完成对应的IO操作
  • 优点:

    • 没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
    • 效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
    • 内核和用户空间mmap同一块内存实现

同步&异步

同步和异步描述的是消息通信的机制

  • 同步

    **代码调用IO操作时,必须等待IO操作完成才返回的调用方式。**当一个request发送出去以后,会得到一个response,这整个过程就是一个同步调用的过程。哪怕response为空,或者response的返回特别快,但是针对这一次请求而言就是一个同步的调用。

  • 异步

    **代码调用IO操作时,不必等待IO操作完成才返回的调用方式。**当一个request发送出去以后,没有得到想要的response,而是通过后面的callback、状态或者通知的方式获得结果。可以这么理解,对于异步请求分两步:

    1.调用方发送request没有返回对应的response(可能是一个空的response);

    2.服务提供方将response处理完成以后通过callback的方式通知调用方。

    对于1. 而言是同步操作(调用方请求服务方),对于2. 而言也是同步操作(服务方回调调用方)。从请求的目的(调用方发送一个request,希望获得对应的response)来看,这两个步骤拆分开来没有任何意义;而需要结合起来看,这整个过程就是一次异步请求

    异步请求有一个最典型的特点:需要callback、状态或者通知的方式来告知调用方结果。

阻塞&非阻塞

阻塞和非阻塞描述的是程序在等待调用结果(消息,返回值)时的状态。函数调用的机制

  • 阻塞

    阻塞调用是指调用方发出request的线程因为某种原因(如:等待系统资源)被服务方挂起,当服务方得到response后就唤醒挂起线程,并将response返回给调用方。

  • 非阻塞

    非阻塞调用是指调用方发出request的线程在没有等到结果时不会被挂起,直到得到response后才返回。

阻塞和非阻塞最大的区别是在调用方线程是否会被挂起

Unix系统下的五种I/O模型

  • 阻塞式I/O

    在这里插入图片描述

  • 非阻塞式I/O

在这里插入图片描述

  • I/O复用

    在这里插入图片描述

  • 信号驱动式I/O

    在这里插入图片描述

  • 异步I/O
    在这里插入图片描述

select+回调+事件循环实现IO多路复用

import socket
from urllib.parse import urlparse
from selectors import DefaultSelector, EVENT_WRITE, EVENT_READ
import time

selector = DefaultSelector()
urls = []
STOP = False


class Fetcher(object):
    def __init__(self):
        self.data = b""
        self.host = None
        self.path = None
        self.client = None
        self.spider_url = None

    def connected(self, key):
        selector.unregister(key.fd)
        self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(self.path, self.host).encode("utf8"))
        selector.register(self.client.fileno(), EVENT_READ, self.readable)

    def readable(self, key):
        d = self.client.recv(1024)
        if d:
            self.data += d
        else:
            selector.unregister(key.fd)
            data = self.data.decode("utf8")
            html_data = data.split("\r\n\r\n")[1]
            print(html_data)
            self.client.close()
            urls.remove(self.spider_url)
            if not urls:
                global STOP
                STOP = True

    def get_url(self, url):
        self.spider_url = url
        url = urlparse(url)
        self.host = url.netloc
        self.path = url.path

        if self.path == "":
            self.path = "/"

        # 建立socket连接
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client.setblocking(False)

        try:
            self.client.connect((self.host, 80))
        except BlockingIOError as e:
            pass

        # 注册
        selector.register(self.client.fileno(), EVENT_WRITE, self.connected)


def loop():
    """
    事件循环,不停的请求socket的状态并调用对应的回调函数
    模式: 回调+事件循环+select/poll/epoll
    """
    # 1. selector本身是不支持register模式的
    # 2. socket状态变化后的回调是由程序员完成的

    # while True:
    while not STOP:
        ready = selector.select()
        for key, mask in ready:
            call_back = key.data
            call_back(key)


if __name__ == "__main__":
    # fetcher = Fetcher()
    start = time.time()
    for seq in range(20):
        url = "http://shop.projectsedu.com/goods/{}/".format(seq)
        urls.append(url)
        fetcher = Fetcher()
        fetcher.get_url(url)
    loop()
    print(time.time()-start)

参考链接

[1] IO多路复用机制

[2] Java IO多路复用机制详解

[3] Python高级核心技术97讲

[4] 同步异步/阻塞非阻塞

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值