• python的异步编程、IO多路复用、协程


    一、异步编程

    1、同步、异步

    • 函数或方法调用的时候,被调用者是否得到最终结果的,直接得到最终结果的,就是同步调用
    • 不直接得到最终结果的,就是异步调用
    • 同步就是我让你打饭,你不打好给我不走开,直到你打饭给了我
    • 异步就是我让你打饭,你打着,我不等你,但是我会盯着你,你打完,我会过来拿走,异步并不保证多长时间打完饭

    2、阻塞、非阻塞

    • 函数或方法调用的时候,是否立刻返回,立即返回就是非阻塞调用,不立即返回就是阻塞调用
    • 同步、异步与阻塞、非阻塞不相关,同步异步强调的是结果,阻塞非阻塞强调的是时间,是否等待
    • 阻塞与非阻塞的区别在于,调用者是否还能干其他事,阻塞、调用这就只能干等
    • 非阻塞调用者可以先去忙会别的,不用一直等

    3、联系

    • 同步阻塞,我啥事不干,就等着你打饭给我,打饭时结果,而我啥事不干一直等,同步加阻塞
    • 同步非阻塞,我等着你打饭,但我可以玩会儿手机,看看电视,打饭是结果,但我不一致等着
    • 异步阻塞,我要打饭,你说等叫号,并没有返回饭给我,我啥事不干,就等着饭好了叫我
    • 异步非阻塞,我要打饭,你说等叫号,并没有返回饭给我,我在旁边看电视干其他事情,非阻塞

    二、同步IO,异步IO,IO多路复用

    1、IO两个阶段

    • IO过程分两个阶段:数据准备阶段; 内核空间复制回用户进程缓冲区阶段
    • 发生IO的时候:内核从输入设备读写数据;进程从内核复制数据

    2、IO模型

    • 同步IO:同步IO模型包括阻塞IO、非阻塞IO、IO多路复用
    • 阻塞IO:进程等待(阻塞),直到读写完成

    3、非阻塞IO

    • 进程调用read方法操作,如果IO设备没有准备好,立即返回error,进程不阻塞,不调用
    • 如果内核已经准备好,就阻塞,然后复制数据到用户空间
    • 第一阶段数据没有准备好,就先忙别的,等会再来看看,检查数据是否准备好了的过程就是非阻塞
    • 第二阶段是阻塞,即内核空间和用户空间之间复制数据是阻塞的

    4、IO多路复用

    • 所谓IO多路复用,就是同时监控多个IO,有一个准备好了,就不需要等了开始处理,提高了同时处理IO的能力

    5、异步IO

    • 进程发起异步IO请求,立即返回,内核完成IO的两个阶段,内核给进程一个信号

    三、python中的IO多路复用

     1、IO多路复用

    • 大多数操作系统都支持select和poll

    2、python的select库

    • 实现了select,poll系统调用,这个基础上操作系统都支持,部分实现了epoll,底层的IO多路复用模块
    • 开发中的选择,完全跨平台,使用select、poll,但是性能较差
    • 针对不同操作系统自行选择支持的技术,这样做会提高IO处理的性能

    3、selectors库

    • 3.4版本提供这个库,高级IO复用库
    • selectors.DefaultSelector返回当前平台最有效,性能最高的实现
    • 但是,由于没有实现Windows下的IOCP,所以,只能退化为select
    • abstractmethod register(fileobj,events,data=None)为selection注册一个文件对象,监视它的IO事件
    • fileobj被监视文件对象,例如socket对象
    • events事件,该文件对象必须等待的事件
    • data可选的与此文件对象相关联的不透明数据,例如这可以用来储存每个客户端的会话ID
    import threading
    import socket
    import selectors
    
    def accept(sock, mask):
        '''mask:事件掩码'''
        conn, addr = sock.accept()
        conn.setblocking(False)  #不阻塞
    
        #关注conn
        key = selector.register(conn, selectors.EVENT_READ, read)
        print(key)
    
    def read(conn, mask):
        data = conn.recv(1024)
        msg = "Your msg = {}".format(data.decode())
        conn.send(msg.encode())
    
    print('accept= {}'.format(accept))
    print('read= {}'.format(read))
    
    selector = selectors.DefaultSelector()
    
    sock = socket.socket()
    addr = ('127.0.0.1', 8888)
    sock.bind(addr)
    sock.listen()
    print(sock)
    
    sock.setblocking(False)  # 非阻塞
    # 监控sock,当其可读时调用accept函数,返回
    key = selector.register(sock, selectors.EVENT_READ, accept)
    print(key)  # 文件对象
    
    e = threading.Event()
    
    def work(event:threading.Event):
        while not e.is_set():
            events = selector.select()  #select阻塞
            if events:
                print('event = {}'.format(events))  #(key,event)二元组列表
            for key,mask in events:
                print('key = {}'.format(key))
                print('mask = {}'.format(mask))
                callback = key.data
                print('callback = {}'.format(callback))
                callback(key.fileobj,mask)  #回调
    
    threading.Thread(target=work, name='work', args=(e,)).start()
    
    while not e.is_set():
        cmd = input('>>>>>>')
        if cmd.strip() == 'quit':
            e.set()
            fobjs = []
            print(selector.get_map().items())
            for fobjs,key in selector.get_map().items():
                print(fobjs,key)
                print(key.fileobj)
                key.fileobj.close()
                fobjs.append(fobj)
            for x in fobjs:
                selector.unregister(x)
            selector.close()


    四、asyncio

    • 3.4版本加入标准库,asyncio底层基于selectors实现,看似库,其实就是个框架,包含异步IO,事件循环,协程,任务等

     

    举例1:问题引出
        
        def a():
            for x in range(3):
                print(x)
    
        def b():
            for x in 'abc':
                print(x)
    
        a()
        b()
    
        这是一个串行的程序,单线程中根本没有做
        
        
    举例2:    使用多线程并行
    
        import threading
        import time
    
        def a():
            for x in range(3):
                time.sleep(0.001)
                print(x)
    
        def b():
            for x in 'abc':
                time.sleep(0.001)
                print(x)
    
        threading.Thread(target=a, name='a').start()
        threading.Thread(target=b, name='b').start()
            
        
    举例3:使用多进程
            
        import multiprocessing
        import time
    
        def a():
            for x in range(3):
                time.sleep(0.001)
                print(x)
    
        def b():
            for x in 'abc':
                time.sleep(0.001)
                print(x)
    
        if __name__ == "__main__":
            multiprocessing.Process(target=a, name='a').start()
            multiprocessing.Process(target=b, name='b').start()
        
        
    举例4:  生成器版本
    
        def a():
            for x in range(3):
                print(x)
                yield
    
        def b():
            for x in 'abc':
                print(x)
                yield
    
        a = a()
        b = b()
    
        for i in range(3):
            next(a)
            next(b)    
        
        上例在线程内通过生成器完成了调度,让两个函数都有几乎执行,这样的调度不是操作系统的进程
        线程完成的,而是用户自己设计的
        

    1、事件循环 

    • 事件循环是asyncio提供的核心运行机制
    • asyncio.get_event_loop() : 返回一个事件循环对象,是asyncio.BaseEventLoop的实例
    • AbstractEventLoop.stop() : 停止运行事件循环
    • AbstractEventLoop.run_forever() : 一直运行,直到stop()
    • AbstractEventLoop.run_until_complete(future) : 运行直至Future对象运行完
    • AbsractEventLoop.close() : 关闭事件循环
    • AbstractEventLoop.is_running() : 返回事件循环的是否运行
    • AbstractEventLoop.close() : 关闭事件循环

    2、协程

    • 协程不是进程,也不是线程,它是用户空间调度的完成并发处理的方式
    • 进程、线程由操作系统完成调度,而协程是线程内完成调度,它不需要更多的线程,自然也没有多线程切换带来的开销
    • 协程是非抢占式调度,只有一个协程主动让出控制权,另一个协程才会被调度
    • 多CPU下,可以使用多进程和协程配合,既能进程并发又能发货协程在单线程中的优势,python中协程是基于生成器的

    3、协程的使用

    • 3.4引入asyncio,使用装饰器
    举例
    
        import asyncio
    
        @asyncio.coroutine
        def sleep(x):  #协程函数
            for i in range(3):
                print('sleep {}'.format(i))
                yield from asyncio.sleep(x)
    
        loop = asyncio.get_event_loop()
        loop.run_until_complete(sleep(3))
        loop.close()    
        
        将生成器函数转换成协程函数,就可以在事件循环中执行了
        
    举例2
    
        import asyncio
    
        async def sleep(x):  #协程函数
            for i in range(3):
                print('sleep {}'.format(i))
                await asyncio.sleep(x)
    
        loop = asyncio.get_event_loop()
        loop.run_until_complete(sleep(3))
        loop.close()
            
        async def用来定义协程函数,iscoroutinefunction()返回Ture,协程函数中可以不包含await,async关键字,但不能使用yield关键字
        ,
        如果生成器函数调用返回生成器对象一样,协程函数调用也会返回一个对象称为协程对象,iscoroutine()返回True
    
    
    举例3.5版本开始,python提供关键字async,await,在语言上原生支持协程
    
    import asyncio
    import threading
    
    async def sleep(x):
        for i in range(3):
            print('sleep {}'.format(i))
            await asyncio.sleep(x)
    
    async def showthread(x):
        for i in range(3):
            print(threading.enumerate())
            await asyncio.sleep(2)
    
    loop = asyncio.get_event_loop()
    tasks = [sleep(3), showthread(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
  • 相关阅读:
    python学习之ajax和可视化管理工具
    操作系统-保护模式中的特权级下
    redis 分布式锁的 5个坑,真是又大又深
    数据库之数据表控制语句
    【NoSQL】Consul中服务注册的两种方式
    netstat命令使用方法以及详解
    Dockerfile与Dockerfile实战
    Spring boot+redis实现消息发布与订阅
    怎么寻回位置不可用移动硬盘的数据
    python字符前面u,r,f等含义
  • 原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11453986.html
Copyright © 2020-2023  润新知