zoukankan      html  css  js  c++  java
  • 从IO的角度深入理解Select、Poll、Epoll的区别推理

      近期刚学习IO多路复用的知识,还有看了django和flask框架WSGIServer的源码,对源码中使用的selector模块比较好奇,也就去稍微深入看了一下个方面资料和相关视频及底层实现,梳理出这篇文章。

      一、Python中起高可用socket服务端的常用三种方式

      在初始我们写一个socket服务端, 如果要供多人同时连接使用的话,有几大方式如在接收消息部分使用多线程,使用协程, 或者是多进程实现socket服务端 。

    socket客户端实现, 用于连接测试服务端

    import socket
    import time
    
    sc = socket.socket()
    sc.connect(('127.0.0.1', 8000))
    
    while True:
        sc.send(b'hello word')
        data = sc.recv(1024)
        print(data)
        time.sleep(1)
    

      

    1)多进程实现socket服务端

    import socket
    from multiprocessing import Process
    import time
    
    
    sc = socket.socket()
    sc.bind(('127.0.0.1', 8000))
    sc.listen(5)
    sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    
    def recv_data(conn):
        while True:
            data = conn.recv(1024)
            if data:
                print(data)
                conn.sendall(data.upper())
    
    
    while True:
        conn, addr = sc.accept()
        if conn:
            Process(target=recv_data, args=(conn,)).start()
        time.sleep(1)
    

      使用多进程实现socket服务端的优缺点

    优点:解决单进程单线程无法多客户端连接的问题

    缺点:开多进程消耗的资源比较大,并且操作系统多进程数量有限制

    2)多线程实现socket服务端

    # 多线程socket服务端

    import socket import threading import time sc = socket.socket() sc.bind(('127.0.0.1', 8000)) sc.listen(5) sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) def recv_data(conn): while True: data = conn.recv(1024) if data: print(data) conn.sendall(data.upper()) while True: conn, addr = sc.accept() if conn: threading.Thread(target=recv_data, args=(conn,)).start() time.sleep(1)

      使用多线程实现socket服务端的优缺点

    优点: 可以满足多客户端连接,实现简单, 比多进程更小的资源的消耗

    缺点: 开多线程耗资源,且线程间的切换有性能消耗,不能无限开

    3)使用协程实现socket服务端

    import time
    import socket
    import gevent
    from gevent import monkey
    monkey.patch_all()
    
    
    sc = socket.socket()
    
    sc.bind(('127.0.0.1', 8000))
    sc.listen(5)
    sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    
    def recv_data(conn):
        while True:
            data = conn.recv(1024)
            if data:
                print(data)
                conn.sendall(data.upper())
    
    
    while True:
        conn, addr = sc.accept()
        if conn:
            gevent.spawn(recv_data, conn)
        time.sleep(1)
    

      使用协程实现socket服务端优缺点:

    优点: 协程是微线程,多个协程在一个线程内切换,占用资源最少,并且在socket这种IO密集型的服务中效率很高,有时速度优于多线程socket实现

    缺点:比起多进程和多线程实现是基本没有缺点,唯一是无法利用多CPU,在计算密集型服务时吃力

    以上三种socket服务端的实现方式都存在的缺点是,如果有1W个连接时,单次就会有1W次IO操作,会有操作系统层面的1W次系统调用,会有比较大的系统调用切换的消耗,这就引出我们的IO多路复用。

      二、IO多路复用之Select、Poll、Epoll及其区别

    1)Select和Poll和Epoll的用法

    其实Select和Poll的区别不大,唯一区别是Select对有最大连接数限制1024这个数字是可以修改的,而Poll是基于链表结构的没有最大连接数限制。

    import selectors
    import socket
    
    
    select = selectors.DefaultSelector()
    
    
    def recv_data(conn, mask):
        data = conn.recv(1024)
        if data:
            print(data)
            conn.sendall(data.upper())
        else:
            select.unregister(conn)
            conn.close()
    
    
    def accept(sc, mask):
        conn, addr = sc.accept()
        conn.setblocking(False)
        select.register(conn, selectors.EVENT_READ, recv_data)
    
    
    sc = socket.socket()
    sc.bind(('127.0.0.1', 8000))
    sc.listen(5)
    sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sc.setblocking(False)
    
    select.register(sc, selectors.EVENT_READ, accept)      # epoll的话相当于向内核开辟一个空间放文件描述符
    
    while True:
        read = select.select(timeout=1)                    # 相当于遍历文件描述符
        for key, mask in read:
            callback = key.data
            callback(key.fileobj, mask)
    

      由于我们使用的python自带的selectors模块,代码中 select = selectors.DefaultSelector() 会根据操作系统的不同实例化合适的Select或者Poll或者Epoll。

      我们知道很多时刻操作系统的进程和线程的调度策略为: 时间片轮转调度,也就是每个线程在时间片内被cpu调度执行,根据这基础我们进行分析,假设我们有1w个线程并发执行(不是并行哦)这样单位时间内就会调度操作系统内核交互1W次,也就产生了1W次IO,如果把这1W次IO变成1次IO,那性能是不是提升很多,而select和epoll就是这样做的,它把这1W个文件描述符(或者理解成调用)放到一个数组或者链表中,一次传递给操作系统内核,然后内核内的线程去循环这个数组,去执行相应的指令,然后执行完毕后,操作系统用户态再拿回这个文件描述符数组,然后遍历取其中的结果,这样就从1W次IO变成了1次IO了。

    多线程下的IO模型

    select和poll下IO图解

      

      我们先说select和epoll的优缺点:

    优点: 可以减少操作系统用户态和内核态IO的次数,统一监控多个IO操作,然后遍历获取结果。

    缺点: 每次都要传递一个大的数组列表, 还有每次都要多数组列表进行遍历获得结果

    所以在上述缺点的情况下Epoll诞生了:

      epoll会在操作系统内核中开辟一个空间,然后每次系统调用就会把新的文件描述,传递给内核(只传递一次),然后内核会开另一个线程去监控内核中的文件描述符,在有返回结果后,它会结果返回放到另一个空间(文件描述符活跃),此时用户态只会遍历活跃状态的文件描述符,这样用空间换时间效率提升,主要体现在:内核多个线程并发处理文件描述符,每次只遍历活跃的文件描述符。

    epoll IO模型

    至此告一段落,后续还需补充挺多东西,如果操作系统的IO知识:

    1、操作系统IO知识,什么是用户态,内核态

    2、操作系统进程线程的调度策略

    3、操作系统的系统调用、中断和异常

    4、还有select函数底层实现 可以在linux中用man函数调用查看解释

    5、什么是文件描述符,操作系统中一切皆文件

    等等一些列操作系统方面的知识

  • 相关阅读:
    mojo 接口示例
    MojoliciousLite: 实时的web框架 概述
    接口返回json
    centos 6.7 perl 版本 This is perl 5, version 22 安装DBI DBD
    centos 6.7 perl 5.22 安装DBD 需要使用老的perl版本
    商业智能改变汽车行业
    商业智能改变汽车行业
    读MBA经历回顾(上)目的决定手段——北漂18年(48)
    perl 升级到5.20版本
    Group Commit of Binary Log
  • 原文地址:https://www.cnblogs.com/lifei01/p/13143286.html
Copyright © 2011-2022 走看看