zoukankan      html  css  js  c++  java
  • Python39 IO模型 1阻塞IO 2.非阻塞IO 3. 多路复用 4.异步IO

    复习

    并发编程------复习

    框架
    1.多道技术--基础
    2.进程
    3.线程
    4.GIL锁
    5.进程池与线程池
    6.异步同步--指的是任务的执行方式
    7.异步回调
    8.协程
    9.猴子补丁--协程的使用

    目录
    1.多道技术--基础
    ----空间复用
    ----时间复用
    2.进程
    ----为什么使用进程
    ----进程的两种使用方式(开启子进程)
    ----守护进程
    ----常用属性和方法
    ----僵尸和孤儿进程
    ----IPC进程间通讯
    ---------共享文件
    ---------共享内存区域(主要方式)
    ------------1.Manager
    ------------2.Queue
    ---------管道
    ---------socket
    ----互斥锁
    ----消费者生产者模型
    3.线程
    ----什么是线程?
    ----线程与进程对比
    ----守护线程
    ----使用方式(开启子线程两种方式)
    ----常用方法
    ----线程队列(线程间通讯)
    ------Queue
    ------lifoQueue堆栈队列,先进后出
    ------PriorityQueue优先级队列
    ----线程锁
    4.GIL锁
    ----全局解释器锁
    ----造成的问题
    ----带来的好处
    ----如何避免带来的性能影响
    ----与自定义锁的区别
    ----加锁和释放
    5.进程池与线程池
    ----什么是 进程池与线程池
    ----为什么使用
    ----使用--创建池
    6.异步同步--指的是任务的执行方式
    ----同步
    ----异步
    ----如何实现异步
    7.异步回调
    ----什么是异步回调
    8.协程
    ----什么是协程
    ----使用协程好处
    ----协程缺点
    ----终极方案---进程+线程+协程
    ----协程与多线程对比
    9.猴子补丁--协程的使用
    ----gevent需自己安装
    ------1.先打补丁
    ------2.提交任务
    ------3.必须保证主线程不会结束---join使用


    内容:详解
     
    1.多道技术
    ---- 空间复用
    同一时间在内存中同时加载多个程序数据,其内存之间相互隔离
    ----时间复用
    切换+保存状态
    切换的两种情况:
    ​     1.一个进程遇到了IO操作时,切换到另一个进程,
    ​     2.时间片用完后,也会被强行切换 
    总结:多道技术的出现使计算机可以并发执行任务 
     
    2.进程:
    正在运行的程序 ,是一个资源单位, 包含程序运行的所有资源 
    ----为什么使用进程:
    ​ 为了并发的执行多个任务,例如TCP中客户端的并发处理 
    ----两种使用方式 
    ​    创建Process实例
    ​    继承Process类
    注意:开启进程的代码必须放在 判断下面 , 因为windows平台开启子进程时,会导入代码执行一遍 来回去要执行的任务 
     
    ----守护进程:
    ​ 被守护进程结束时守护进程也会随之结束 
    ----常用属性和方法
    ​ join  提高子进程的优先级 使得子进程先于父进程执行  父进程需要等待子进程完成后才能继续执行 
    ​ daemon 设置为守护进程
    ​ is_alive 是否存活 
    ​ pid   进程id
    ​ terminate  终止进程 
    ​ exitcode 获取进程的退出码
    ​ name 名字 
    ----僵尸和孤儿
    孤儿  父进程先于子进程结束了,子进程会被操作系统接管 
    僵尸  在linux有一个机制,可以保证父进程在任何时候都可以访问到子进程的一些信息,所以子进程结束后并不会立即清除所有数据 ,这时候就是僵尸进程 
    僵尸进程会占用一些系统资源,需要父进程调用waitpid来进行清理, 
    python会自动清理僵尸进程 
     
    ---- IPC
    ​ 进程间通讯 
    ​ 因为每个进程之间内存是物理隔离,很多时候我们需要将数据讲给另外一个进程 ,例如:美团要把订单信息交给支付宝
    ​    1.共享文件
    ​  特点:数据量没什么限制,但是读写速度慢 
    ​    2.共享内存区域
    ​  特点:数据量不能太大,速度快   
    ​    3.管道
    ​  单向通讯,传输的是二进制 
    ​    4.socket 
    ​   编程较复杂
       主要方式:共享内存
    ​       1.Manager 提供一系列常用的数据结构,但是没有处理锁的问题 
    ​       2.进程Queue  是一种特殊的容器,先进先出,并且进程队列支持IPC    已经处理好锁了
    ---- 互斥锁
    ​ 相互排斥的锁,mutex 
    ​ 锁是什么: 本质就是一个标志,可以限制代码是否能够执行 
    ​ 为什么需要锁:多个进程要操作同一个资源时,可能造成数据错乱 
    ​ 加锁会导致并发变成串行,降低了效率,保证了数据安全 
    ​ 
    -----与join的区别 
    ​ join会使得整个进程代码全部串行,并且主进程也无法继续执行 
    ​ 锁可以控制部分代码串行,其余任然并发,效率比join高  
    ​ 锁的粒度越小效率越高
     
    -----消费者生产者模型 
    ​ 要解决的问题,生产者用户消费者处理能力不平衡,  如果串行执行任务 效率低下
    ​ 解决的方案:
    ​    1.将生产者与消费者节考耦合
    ​    2.将双方并发执行 
        3.提供一个共享的容器 
    ​    4.生产者将数据放入容器
    ​    5.消费者从容器获取数据来处理 
    ​    
       是否可以使用多线程来完成生产者消费者模型  必须可以
       在线程中需不需要使用队列呢??         建议使用 
     
    -------抢票案例:
    ​ 数据出了问题,一张票 卖给了多个人  ,原因就是因为并发了 
    ​ 加锁解决:  将并发修改的代码加锁变成串行
    3.多线程
    ----- 线程是: CPU最小的执行单位 ,(操作系统最小调度运算单位),是一个固定执行过程的总称
      一个进程至少包含一个线程,称之为主线程 ,是由操作系统自动开启的 
    ​ 运行过程中自己开启的线程 称之为子线程
    ​ 线程间没有父子关系   ,  例如a-b-c   b和c都是a的子线程 
    ​ 
    -----线程对比进程:
    ​  开启速度快,开销小 
    ​  同一个进程中所有线程数据共享
     
    ---- 守护线程:
    ​  守护线程会在所有非守护线程结束时随之结束,当然守护线程可能提前结束了 
    ​ 
    ----  使用方式:
    ​  与进程一样,开线程的代码可以放任何位置
    ----  常用方法:
    ​  currentthread()  获取当前的线程对象
    ​  enumerate()   获取所有运行中的线程对象
    ​  active_count   获取存活的线程数量
    ​ 
    ----- 线程队列
    ​ queue 
    ​  Queue  普通队列
    ​  LifoQueue  先进后出队列 模拟堆栈
    ​  PriorityQueue  优先级队列    可以比较大小的数据都能存到其中   取出时按照从小到大取出
    ​   运算符重载  可以使自定义对象支持 算术运算符     
    -----线程锁
    ​ Lock 互斥锁
    ​ Rlock 递归锁    同一个线程可以多次锁定或解锁 ,锁了几次就解几次 
    ​ semaphore  信号量   可以限制同一时间多少线程可以并发执行
    ​ 死锁问题  当一个资源的访问,需要具备多把锁时,然而不同的锁被不同线程持有了,陷入相互等待中 
    ​  1.,尽量使用一个锁 , 设置超时 释放手里的锁, 
    ​  2.抢锁时 按顺序抢 
    4.GIL锁
    ​------ GIL全程 全局解释器锁 ,是一把互斥锁,是非常重要的,为了防止多个本地线程同一时间执行python的字节码,
    ​ 因为Cpython的内存管理不是线程安全的(非线程安全的),越来越多的特性依赖于这把锁, 如果去掉这个锁的话, 会有很代码需要重构, 并且需要程序自己来处理很多的安全问题,这是非常复杂的 
    ------造成的问题
    ​ cpython多个线程不能并行,丧失了多核优势 
    ----- 带来的好处
    ​ 保证了线程安全,
    ----- 如何避免带来的性能影响
    ​ 首先判断任务的类型 ,分IO密集型   计算密集型 
    ​ 是IO密集型任务,使用多线程即可,由于大部分时间消耗在IO等待上了,所以影响不大
    ​ 计算密集型,只能开启多进程 
    ------与自定义锁的区别
    ​ GIL只能 保证解释器级别数据安全,如果我们自己开启了一些不属于解释器的资源例如文件. 必须自己加锁来处理 
    -------加锁和释放
    ​ 拿到解释器要执行代码时立即加锁
    ​ 遇到IO时解锁    
    ​ CPU时间片用完了   注意解释器的超时时间 与CPU的超时时间不同   为100nm 
     
    5.进程池  线程池 
    ​ 池即容器
    ​ 线程池 即存储线程的容器 
    ​ -----为什么使用线程池
    ​  1.可以限制线程 数量      通过压力测试 来得出最大数量 
    ​  2.可以管理线程的创建以及销毁 
    ​  3.负责任务的分配 
    ​ -----使用
    ​  创建池
    ​  submit提交任务   异步任务    将返回future对象    调用add_done_callback 可以添加回调函数 
    ​  在任务结束时还会自动调用 回调函数并传入 future本身, 调用result()可以拿到任务的结果 
    ​  
    ​  不常用的两种方式
    ​  shutdown  可以关闭线程池,会阻塞直到所有任务全部完成 
    ​  直接调用result  如果任务没有完成会进入阻塞状态  
    ​ 
    6.  异步同步 
    ​ 同步: 任务提交后必须原地等待任务执行结,才能继续执行 
    ​ 异步: 提交任务后可以立即执行后续代码  
    ​ 异步效率高
    ​ ----如何实现异步:
    ​  多线程,多进程
    ​ -----同步异步指的是   任务的执行方式 
    ​ 
     
    7.异步回调
    ​ 异步回调  本质就是一个普通函数,该函数会在任务执行完成后自动被调用 
    ​ 线程池, 谁有空谁处理
    ​ 进程池,都是在父进程中回调 
     
    8. 协程
    ​ 协程本质是在单线程下实现并发
    ​ 协程是一种轻量级线程,也称为微线程,可以由应用程序自己来控制调度
     
    ​ -----好处:
    ​  可以再一个任务遇到IO操作时,自主切换到自己进程中其他线程   
    ​  如果任务足够多,就可以充分利用CPU的时间片 
    ​  
    ​ 
    ​ -----缺点:
    ​  仅适用于IO密集型任务 ,计算密集型,如果是单线程话的串行效率更高 ,建议使用多进程来处理 
    ​  当单个任务耗时较长时  协程效率反而不高
    ​   
    我们目的就是尽可能的提高效率 
    ​ 进程 线程  协程 
    ​ 可以使 多进程 + 线程 + 协程      UWSGI
    协程对比多线程:
    ​ 线程池可以解决一定的并发数量,但是如果并发量超过了机器能承受最大限制,线程池就出现瓶颈了
    ​ 
    9.猴子补丁-- 协程的使用
    ​ gevent   需要自己安装 
    ​ 1.先打补丁     (本质是将原本阻塞的代码替换成非阻塞的代码)
    ​ 2.gevent.spawn(任务)  来提交任务 
    ​ 3.必须保证主线不会结束 使用join  或是join  all
     
     
    今日内容:
    IO模型
    1阻塞IO  
    2.非阻塞IO 
    3. 多路复用
    4.异步IO
    # IO模型
    ​ 模型就是解决某个问题的套路 
    ​ IO问题:
    ​  输入输出 
    ​  我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时,  如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等
    ​ 将当前进程阻塞住,切换至其他进程执行,等到按下回车键,拿到了一个用户名,再唤醒刚才的进程,将状态调整为就绪态
     
    以上处理方案  就称之为阻塞IO模型
     
    存在的问题:
    ​ 当执行到recv时,如果对象并没有发送数据,程序阻塞了,无法执行其他任务
    解决方案:
    ​ 多线程或多进程,
    ​   当客户端并发量非常大的时候,服务器可能就无法开启新的线程或进程,如果不对数量加以限制 服务器就崩溃了
    ​ 线程池或进程池
    ​  首先限制了数量 保证服务器正常运行,但是问题是,如果客户端都处于阻塞状态,这些线程也阻塞了
    ​ 协程:
    ​  使用一个线程处理所有客户端,当一个客户端处于阻塞状态时可以切换至其他客户端任务  
     
    ## 非阻塞IO模型
    ​ 阻塞IO模型在执行recv 和 accept  时 都需要经历wait_data 
    ​ 非阻塞IO即 在执行recv 和accept时 不会阻塞 可以继续往下执行
    ​ 
    ​ 如何使用:
    ​  将server的blocking设置为False 即设置非阻塞 
    ​  
    ​ 存在的问题 :
    ​  这样一来 你的进程 效率 非常高 没有任何的阻塞
    ​  很多情况下 并没有数据需要处理,但是我们的进程也需要不停的询问操作系统  会导致CPU占用过高
    ​  而且是无意义的占用 
    案例:
    服务器:
    import socket
    import time
    server = socket.socket()
    server.bind(("192.168.13.103",1688))
    server.listen()
    server.setblocking(False) # 默认为阻塞    设置为False 表示非阻塞
    # 用来存储客户端的列表
    clients = []
    # 链接客户端的循环
    while True:
        try:
            client,addr = server.accept()   # 接受三次握手信息
            # print("来了一个客户端了.... %s" % addr[1])
            # 有人链接成功了
            clients.append(client)
        except BlockingIOError as e:
            # print("还没有人连过来.....")
            # time.sleep(0.5)
            # 服务你的客人去
            for c in clients[:]:
                try: # 可能这个客户端还没有数据过来
                    # 开始通讯任务
                    data = c.recv(2048)
                    c.send(data.upper())
                except BlockingIOError as e:
                    print("这个客户端还不需要处理.....",)
                except ConnectionResetError:
                    # 断开后删除这个客户端
                    clients.remove(c)
            print("=======================",len(clients))
    ```
     
    # 多路复用IO模型
    ​ 
    import socket
    import select
    server=socket.socket()
    server.bind(("127.0.0.1",1688))
    server.listen()#三次握手未完成,半连接数
    rlist=[server,]
    '''
    # 将需要检测(是否可读==recv)的socket对象放到该列表中
    # accept也是一个读数据操作,默认也会阻塞 也需要让select来检测
    # 注意 select最多能检测1024个socket 超出直接报错 这是select自身设计的问题 最终的解决方案epoll
    '''
    wlist=[]
    '''
    # 将需要检测(是否可写==send)的socket对象放到该列表中
    # 只要缓冲区不满都可以写
    '''
    #存储需要发送的数据,等待select 检测后,再进行发送
    msgs=[("socket","msg")]
    print("start")
    while True:
    # 会阻塞等到 有一个或多个socket 可以被处理
    readable_list,writeable_list,_=select.select(rlist,wlist,[])
    print("%s个socket可读"%len(readable_list),"%s个socket可写"%len(writeable_list),)
    """
    readable_list 中存储的是已经可以读取数据的socket对象 可能是服务器 可能是客户端
    """
    #处理可读列表
    for soc in readable_list:
    if soc==server:
    #服务器的处理
    client,addr=server.accept()
    #将新连接socket对象加到待检测列表
    rlist.append(client)
    else:
    try:
    #客户端的处理
    data=client.recv(1024)
    if not data:
    # 如果对方下线 关闭socket 并且从待检测列表中删除
    soc.close()
    rlist.remove(soc)
    continue
    # 不能直接发 因为此时缓冲区可能已经满了 导致send阻塞住, 所以要发送数据前一个先这个socket交给select来检查
    # soc.send(data.upper())
    if soc not in wlist:
    wlist.append(soc)
    # 将要发送的数据先存起来
    msgs.append((soc, data))
    except ConnectionRefusedError:
    soc.close()
    # 对方下线后 应该从待检测列表中删除 socket
    rlist.remove(soc)
    wlist.remove(soc)
    # 处理可写列表
    for soc in writeable_list:
    # 由于一个客户端可能有多个数据要发送 所以遍历所有客户端
    for i in msgs[:]:
    if i[0] == soc:
    soc.send(i[1])
    # 发送成功 将这个数据从列表中删除
    msgs.remove(i)
    # 数据已经都发给客户端 这个socket还需不需要检测是否可写,必须要删除
    wlist.remove(soc) # 否则 只要缓冲区不满 一直处于可写 导致死循环
     客户端:一样
    client=socket.socket()
    client.connect(("127.0.0.1",1688))
    while True:
    msg=input("msg:")
    if not msg:continue
    client.send(msg.encode("utf-8"))
    data = client.recv(2048).decode("utf-8")
    print(data)
    ​ 
     晚上总结:
    '''
    IO 模型

    网络传输数据的两个阶段

    send 从应用程序copy到操作系统
    recv 等待数据到达缓冲区 wait_data 然后在从操作系统缓冲区copy应用程序
    wait_data 耗时最长
    我们的目的就等待数据的这一段时间 合理的利用CPU来提高效率

    阻塞IO
    默认情况下就是阻塞IO模型
    当执行recv时 如果对方没有数据到达 那么程序阻塞在原地
    线程池 有最大限制 不能无限的开线程
    来了1000 所以开了1000线程 导致1001 客户端不能正常访问
    而且很有可能很多线程处于阻塞状态,浪费了资源
    可以使用协程
    协程是单线程并发处理,
    检测IO操作 当发生IO操作时 切换到其他任务来执行
    协程的原理是把原本阻塞的操作 换成非阻塞的操作


    非阻塞IO === 非阻塞的网络IO 了解
    非阻塞 即 即使遇到了IO操作 也不会阻塞在原地 会继续往下执行
    server 是一个服务器socket对象
    server.setblocking(Fasle) 设置为非阻塞
    问题是: 每次读取数据时 不一定有数据 为了能够及时处理数据 只能不停的询问 忙轮询
    这种忙轮询的方式,即使没有数据需要处理 也需要不停的循环,造成了无用CPU占用

    多路复用
    假设原本有30个socket 需要我们自己来处理, 如果是非阻塞IO模型,相当于从头开始问道尾,如果没有需要处理的
    回过头来再次重复,

    多路复用解决问题的思路,找一个代理即select,将你的socket交给select来检测
    select 会返回 那些已经准备好的 可读或者可写的socket
    我们拿着这些准备好的socket 直接处理即可

    对比线程池
    避免了开启线程的资源消耗
    缺点:
    同时检测socket不能超过1024


    异步IO
    阻塞IO recv accept 会将当前线程阻塞住 同步
    非阻塞IO recv accept 不会阻塞当前线程 ,没有数据直接抛出异常
    分析 属于同步还是异步?
    recv (wait_data,copy_data) 设置为非阻塞之后 wait_data不会再阻塞
    但是copy_data 也是IO操作 还是会阻塞
    也属于同步

    多路复用 也属于同步IO


    同步 异步 任务的执行方式
    同步IO 执行IO任务的方式
    异步IO

    异步IO
    线程池中的submit 就是异步任务
    异步的特点就是 立马就会返回
    同步翻译为sync 异步async
     
     
    ​  
    ​ 
     
     
    ​ 
    ​ 
     
    ​  
     
     
    ​ 
     
     
    ​ 
     
     
    ​ 
     
    ​  
     
    ​ 
     
    ​ 
     
     
     
     
     
     
     
     
     
     
    ​ 
     
     
     
     
     
    ​ 
     
     
    ​ 
     
    ​ 
     
    ​ 
     
    ​   
    ​   
     
     
     
     
     
     
     
    ​ 
     
     
     
     
     
     
     
     
     
     
     
    ​ 
     
     
    ​ 
     
     
    ​ 
     
     
     
    ​ 
     
     
    ​  
     
     
     
     
     
     
    ​  
    ​ 
     
     
     
     
     
     
     
     
     
     
     
     
     
    ​ 
     
     
    ​ 
     
     
     
     
     
     
    ​  
     
     
     
     
     
    ​ 
     
     
     
     
     
     
     
     
     
     
     
    #### 
     
  • 相关阅读:
    paip.提高开发效率自动数据库SQL备份
    paip.提升开发效率增量备份项目文件
    paip.提升开发效率使用拼音
    paip.asp vbs代码的排版格式化
    paip.asp vbs的代码折叠代码结构查看
    paip.提升用户体验文件查找
    poj3026
    poj3020
    poj1182
    POJ典型算法例题题号
  • 原文地址:https://www.cnblogs.com/llx--20190411/p/11000671.html
Copyright © 2011-2022 走看看