zoukankan      html  css  js  c++  java
  • io多路复用

    io多路复用就是单个线程能处理多个socket连接了,

    从前一个线程要么只能处理连接请求,要么只能处理已经建立的连接的交互。先是主线程负责处理连接请求,收到连接请求后,就开启一个线程,这个线程再专门负责这个连接的交互,比如聊天什么的。。

    from threading import thread
    import socket
    sk=socket.socket()
    sk.bind(('127.0.0.1',8888))
    def func(con):
        '''处理已经建立好的连接的消息,比如聊天,con.recv,con.send什么的'''
        pass
    sk.listen()
    while True:
        con,addr=sk.accept()
        thread(target=func,args=(con,))

    因为切换线程很耗费资源,建立的连接一多了,时间都浪费在切换线程上了。

    于是,就发明了一种机制,可以在一个线程内,处理多个socket连接,叫做io多路复用

    上个例子,我们使用sk对象来接收请求(sk.accept),而用con来接收(con.recv)或发送信息(con.send),但是本质上,他们都可以视为是一种文件描述符,就好比f=open('xx')的f对象一样,都是进行读(sk.accept con.recv f.read)写(con.send f.write())操作,不同的是,sk和con是网络io,而f是磁盘io

    而io多路复用,就是将所有的网络io都放入一个列表中,传给select.select()方法,这个方法就负责观册列表中的所有io,如果有io得到消息了,对于sk来讲就是有新的连接请求了,对于con来讲就是建立好的这个连接发来消息了。那么他就返回这些io,然后我再编写程序,在具体判断这是sk,还是con,如果是sk则执行accept,并将得到的新的连接加入到那个列表中,以方便以后让这个select.select()方法监测。如果是con则recv消息。而如果监测列表中没有io得到消息,则会一直阻塞,直到有io

    产生消息为止在循环执行之前的步骤

    具体的代码是这样:

    import select
    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8003))
    sk.setblocking(False)
    sk.listen()
    print(sk)
    read_lst = [sk]
    while True:   # [sk,conn]
        r_lst,w_lst,x_lst = select.select(read_lst,[],[])
        for i in r_lst:
            if i is sk:
                conn,addr = i.accept()
                read_lst.append(conn)
            else:
                ret = i.recv(1024)
                if ret == b'':
                    i.close()
                    read_lst.remove(i)
                    continue
                print(ret)
                i.send(b'goodbye!')

    注意,在io多路复用中,sk.accept和con.recv不同于线程里的accept recv,后者是阻塞的,因为消息还没有到达内核态,而前者阻塞非常小,因为消息已经到达内核了,只差从内核复制到用户态了 。

    from socket import *
    import selectors
    
    sel=selectors.DefaultSelector()
    def accept(server_fileobj,mask):
        conn,addr=server_fileobj.accept()
        sel.register(conn,selectors.EVENT_READ,read)
    
    def read(conn,mask):
        try:
            data=conn.recv(1024)
            if not data:
                print('closing',conn)
                sel.unregister(conn)
                conn.close()
                return
            conn.send(data.upper()+b'_SB')
        except Exception:
            print('closing', conn)
            sel.unregister(conn)
            conn.close()
    
    
    
    server_fileobj=socket(AF_INET,SOCK_STREAM)
    server_fileobj.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    server_fileobj.bind(('127.0.0.1',8088))
    server_fileobj.listen(5)
    server_fileobj.setblocking(False) #设置socket的接口为非阻塞
    sel.register(server_fileobj,selectors.EVENT_READ,accept) #相当于网select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数accept
    
    while True:
        events=sel.select() #检测所有的fileobj,是否有完成wait data的
        for sel_obj,mask in events:
            callback=sel_obj.data #callback=accpet
            callback(sel_obj.fileobj,mask) #accpet(server_fileobj,1)

    sel_obj.obj则是有消息的这个io,所绑定的回调函数

    sel_obj.fileobj则是有消息的这个io本身 

     selector模块能帮我们根据操作系统自动选择使用那种多路复用,epoll select poll

     具体能实现Io多路复用的有三种

    # IO多路复用
    # select机制 Windows linux 都是操作系统轮询每一个被监听的项,看是否有读操作
    # poll机制 linux 它可以监听的对象比select机制可以监听的多
    # 随着监听项的增多,导致效率降低
    # epoll机制 linux 

    select和poll都是轮询列表中的每个项,看看是否有可读操作,这样列表中的项一朵了,效率就下降,而epoll比selet和poll高端太多。高效很多,他已经不是

     以下转载自知乎

    作者:用心阁
    链接:https://www.zhihu.com/question/28594409/answer/74003996
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    首先,要从你常用的IO操作谈起,比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。


    (图片来源:l
    这样,当服务器需要处理1000个连接的的时候,而且只有很少连接忙碌的,那么会需要1000个线程或进程来处理1000个连接,而1000个线程大部分是被阻塞起来的。由于CPU的核数或超线程数一般都不大,比如4,8,16,32,64,128,比如4个核要跑1000个线程,那么每个线程的时间槽非常短,而线程切换非常频繁。这样是有问题的:
    1. 线程是有内存开销的,1个线程可能需要512K(或2M)存放栈,那么1000个线程就要512M(或2G)内存。
    2. 线程的切换,或者说上下文切换是有CPU开销的,当大量时间花在上下文切换的时候,分配给真正的操作的CPU就要少很多。

    那么,我们就要引入非阻塞I/O的概念,非阻塞IO很简单,通过fcntl(POSIX)或ioctl(Unix)设为非阻塞模式,这时,当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,如EWOULDBLOCK。这样是不会阻塞线程了,但是你还是要不断的轮询来读取或写入。


    (图片来源:l

    于是,我们需要引入IO多路复用的概念。多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。


    (图片来源:l

    这样在处理1000个连接时,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。

    使用select函数的方式如下图所示:
  • 相关阅读:
    [Matlab]二维统计分析图实例
    [Matlab]求解线性方程组
    [图论笔记]基本概念
    [线代笔记]第一章 线性方程组解法
    [Graphviz]一些简单的例子(未完待续)
    [杂记]如何在LaTeX里插入高亮代码
    [c语言]运算符的优先级与结合性
    [算法]连续子数组最大和
    dp超时的另外一种优化,找规律(四塔问题)
    饥饿的牛 线性dp内的区间
  • 原文地址:https://www.cnblogs.com/saolv/p/10162484.html
Copyright © 2011-2022 走看看