一、异步编程
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()