zoukankan      html  css  js  c++  java
  • Python 协程、IO模型

    1.协程(单线程实现并发)
    2.I/0模型
    2.1阻塞I/O
    2.2非阻塞I/O

    知识点一:协程
    协程的目的:是想要在单线程下实现并发(并发看起来是同时运行的)
    并发=多个任务间切换+保存状态(正常情况都是由操作系统来控制的)

    一般情况下都是由操作系统来控制的,现在要实现的就是遇到I/o自己来切换,也叫并发

    优点:在应用程序级别速度要远远高于操作系统的切换
    缺点:多个任务一旦有一个阻塞没有切,整个线程都会阻塞原地,该线程内的其他任务
    都不能执行

    一旦引入协程,就需要检测单线程下所有的IO行为,实际遇到IO就切换,
    少一个都不行,一旦一个任务阻塞了,整个线程就阻塞了,其他的任务即便是
    可以计算,但是也无法运行了

    一个程序没有遇到IO也切,反而会降低效率,应该找到一种让程序遇到IO切,才能提高效率

    gevent模块:模拟识别IO阻塞
    #gevent模块:模拟识别IO阻塞
    import gevent
    from gevent import monkey,spawn;monkey.patch_all() #pathch_all()打补丁就是让gevent能识别所有的IO
    from threading import current_thread
    import time
    
    def eat():
        print('%s eat 1'%current_thread().name)
        # gevent.sleep(2)   #默认只能识别自己模块的Io行为
        time.sleep(3)
        print('%s eat 2'%current_thread().name)
    
    def play():
        print('%s play 1'%current_thread().name)
        # gevent.sleep(1)
        time.sleep(1)
        print('%s play 2'%current_thread().name)
    
    #创建协程对象
    g1=spawn(eat,)    #spawn(函数名,参数1...)  都是传给函数eat的
    g2=spawn(play,)
    
    print(current_thread().name)
    g1.join()  #等待g1结束
    g2.join()  #等待g2结束
    
    
    
    '''输出结果:
    MainThread
    DummyThread-1 eat 1   #假线程
    DummyThread-2 play 1
    DummyThread-2 play 2
    DummyThread-1 eat 2
    
    '''
    
    '''
    而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
    
    from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前
    
    或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

    gevent应用实例一:利用gevent模块改写socket服务端实现:单线程IO切换自动切换

    #服务端
    from gevent import spawn,monkey;monkey.patch_all()
    from socket import *
    from threading import Thread
    
    def talk(conn):
        while True:
            try:
                data=conn.recv(1024)
                if len(data) == 0:break
                conn.send(data.upper())
            except ConnectionResetError:
                break
        conn.close()
    
    def server(ip,port,backlog=5):
        server = socket(AF_INET, SOCK_STREAM)
        server.bind((ip, port))
        server.listen(backlog)
    
        print('starting...')
        while True:
            conn, addr = server.accept()
            spawn(talk,conn)
    
            #原来造线程的方式:
            # t = Thread(target=talk, args=(conn,))
            # t.start()
    
    if __name__ == '__main__':
        # server('127.0.0.1',8080)
        g=spawn(server,'127.0.0.1',8080)
        g.join()
    
    -------------------------------------------------------------------------------
    #客户端
    from socket import *
    import os
    
    client=socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg='%s say hello' %os.getpid()
        client.send(msg.encode('utf-8'))
        data=client.recv(1024)
        print(data.decode('utf-8'))

    知识点二:基于网络的IO模型
    可以分为2大类:
    第一类:
    server.accept()
    第二类:
    conn.recv()
    conn.send()

    1.recv(收消息)
    wait data:等待客户端产生数据--》客户端OS--》网络--》服务端操作系统缓存
    copy data:由本地操作系统缓存中的数据拷贝到应用程序的内存中

    2.send(发消息)
    copy data


    阻塞IO:blocking IO
    blocking IO就是在执行的2个阶段(等待数据和拷贝数据两个阶段)都被block了


    非阻塞IO:non-blocking IO:
    思考:目的就是将将recv()阻塞的这个模型变为非阻塞,
    即如果没有数据先执行其它的任务,并且最好在没有数据时客户端能返回一个
    没有数据的提示信息


    非阻塞IO:的问题
    1.CPU占用率高(多数的询问是无用的)
    2.for循环列表多的情况下会慢
    3.数据得不到及时的处理(因为有时候存在可能刚切换到其他任务,数据就过来了
    这样就不能及时处理)

    非阻塞IO模型改写socket套接字服务端、客户端:

    #服务端
    from socket import *
    import time
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    server.setblocking(False)   #所有的IO行为都变为非阻塞模型,设置为False
    
    conn_l=[]  #保存的是一堆连接,便于后面与每个客户端通信收发消息
    while True:
        #建立连接循环:
        try:
            print('总连接数[%s]' % len(conn_l))
            conn,addr=server.accept()
            conn_l.append(conn)
        except BlockingIOError: #客户建立连接请求,报错信息,捕捉这个异常可以执行下面的代码,即执行其他任务
            # print('客户端没有数据过来,可以执行其他任务')
            del_l=[]   #另外新建一个列表单独存放那些非法数据,方便最后清理这些数据
    
            #建立通信循环:
            for conn in conn_l:
                try:
                    data=conn.recv(1024)  #与accept()建立连接一样,如果没有消息过来,直接
                    if len(data) == 0:     #输入的是空值
                        del_l.append(conn)
                        continue
                    conn.send(data.upper())
                except BlockingIOError:
                    pass
                except ConnectionResetError: #客户端单方面终止连接
                   del_l.append(conn)  #将终止的连接添加到定义的列表里面,最后统一删除这些无效的连接
    
            for conn in del_l:
                conn_l.remove(conn)


    -----------------------------------------------------------------------------------------------------------------
    #客户端:不需要变动
    from socket import *
    import os

    client=socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1',8080))

    while True:
    msg='%s say hello' %os.getpid()
    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data.decode('utf-8'))
     
  • 相关阅读:
    hbuilder外置服务器设置(局域网移动端调试)
    【转载】解决微信OAuth2.0网页授权回调域名只能设置一个的问题
    【转载】如何使用PHP构建一个高性能的弹幕后端服务
    【转载】PHP学习资源整理
    【php基础】php运算符 php取整函数
    Browser 对象(一、history)
    Opencv 图像平滑基础二维离散卷积C++ API
    Opencv 图像平滑基础二维离散卷积 python API
    道格拉斯轨迹抽稀算法Android 百度地图SDK
    Opencv 几何变换
  • 原文地址:https://www.cnblogs.com/yangzhizong/p/9326148.html
Copyright © 2011-2022 走看看