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()

猜你喜欢

转载自www.cnblogs.com/jiangzuofenghua/p/11453986.html