python 3.x asyncio并发编程(异步io并发编程)概念篇及事件循环
asyncio并发编程(异步io并发编程)概念篇及事件循环
- 与同步编码模式的思维有非常大的区别
- 作用:
- 用于解决python中异步IO高并发编程的一个核心模块
- 这个模块在python3.4以后开始引入.该模块也是python最具有野心的一个模块.
- 有了asyncio这个模块,我们在去进行高并发的编程,不管去编写服务端高并发编程(比如web服务器).以及高并发的爬虫都可以胜任.
- 理解asyncio内部完成什么功能???
- asyncio在python中是一个模块,但在某种程度上可以把asyncio理解为一个框架,因为asyncio完成了整套异步io编程模型中最核心的一些功能.
- asyncio的基本功能: 事件循环
- 包含各种特定系统实现的模块化事件循环(比如之前介绍的windows支持的是select,Linux支持的是epoll),所以说asyncio基于这些提供了不同系统都能兼容的事件循环.
- 传输和协议抽象(比如tcp,udp协议的一个抽象).
- 对tcp,udp,ssl,子进程,延时调用以及其他的具体支持.
- 模仿futures模块但适用于事件循环使用的Future类(asyncio也有自己的Future类).
- 基于yield from的协议和任务,可以让你用顺序的方式编写并发代码.
- 当必须使用一个将产生阻塞IO的调用时,有接口可以把这个事件转移到线程池中去运行.
- 模仿threading模块中的同步原语,可以用在单线程内的协程之间.
- 这也就意味着asyncio虽然主要功能是用于做协程调度,但是asyncio也可以将多进程和多线程给他融合进来.所以称之为异步IO库
- 异步IO包含:
- 多进程
- 多线程
- 协程
- 学习asyncio的原因:
- asyncio提供了大量的功能以及接口,这样的话再去解决异步IO并发编程的时候,就不需要自己去完成这些功能.
- 在协程中虽然可以用send()方法向外发送数据,但是协程脱离了事件循环.
- 协程独立存在的话,存在的意义并不大.
- 协程与函数不一样,函数直接可以调用即可,但是协程需要搭配事件循环才能够发挥协程最大的功能.
- asyncio提供了事件循环
- 协程编码模式脱离不了三个要点:也是高并发编程模式中具备的三个要素
- 事件循环
- 回调(在协程模式之下又称之为驱动生成器) (回调在asyncio中变成驱动生成器或者称之为驱动协程)
- IO多路复用(select poll epoll) : IO多路复用是用于事件循环当中的
- 在协程中介绍的函数是可以暂停的
- asyncio是pyhton用于解决异步IO编程的一整套解决方案
- 基于asyncio的框架:
- Tornado,gevent,twisted(twisted目前运用在两个方面是scrapy,django的channels主要用于http2.0开发的)
- Tornado:
- 是以协程+事件循环的方式来完成高并发
- 实现了web服务器:
- 意思就是python中最流行的web框架是django+flask,他俩是传统的阻塞io的编程模型,而且他俩是一个web系统开发框架,他俩本身是不提供web服务器的,也就是说他俩不能完成socket编码的.
- 在真正做django和flask部署的时候,是不会使用django,flask本身提供的一个运行机制的,运行机制是简单的实现socket.只是用来方便程序员调试.
- 在真正部署的时候,会搭配第三方实现socket接口的一些框架(比如uwsgi,gunicorn.最后还会搭配一个nginx)
- 但是tornado不一样,tornado是实现web服务器,所以说在部署tornado的时候是可以直接部署,就是直接启动tornado,至于并发问题我们都不用去管,因为tornado自己会实现,tornado会使用epoll去完成socket连接请求.
- 但是在真正部署的时候,还是会搭配Nginx+tornado.因为Nginx提供了非常多的功能,是tornado本身提供不了的(比如IP限制,log服务,静态文件代理等,这些tornado都没有Nginx方便).
- tornado在使用数据库驱动的时候,不能简单的使用平常所用到的驱动.
- 为什么tornado不能使用传统的阻塞IO驱动呢???
- 了解了asyncio自然就会明白.
- 通过案例async_io_event_loop_01.py ~~~ async_io_event_loop_05.py
- 在tornado里面为什么使用传统的比如pymyaql,mysql client这些驱动是不行的.因为达不到一个并发的效果的.
- 因为pymysql和mysql client的接口都是阻塞的,我们的协程模式时单线程的,只要一个地方阻塞,其他地方的逻辑都运行不了.所以说在协程编码中一定要注意,在做数据库驱动,网络驱动的时候,一定要有一个对应的异步库来完成我们的功能.
- 为什么tornado不能使用传统的阻塞IO驱动呢???
- asyncio与request库能不能结合使用???
- 实际上我们想要利用高并发,我们传统的request库是做不到的,需要自己去找一下有没有适用于我们这种编程模式的一个其他的驱动.当然驱动非常的多,在网上可以找得到.
async_io_event_loop_01.py
1 """ 2 使用asyncio 3 4 最简单的使用方式来完成协程的一个运行 5 """ 6 7 import asyncio 8 import time 9 10 11 async def get_html(url): 12 """ 13 定义一个协程 14 """ 15 print("start get url") 16 # 模拟html请求过程 17 # 注意:不能使用time.sleep(2). 18 # time.sleep()是一个同步阻塞的接口,同步阻塞的接口是不能使用在协程里面的,这一点非常的关键. 19 # asyncio.sleep(2)是一个耗时的操作. 20 # 注意: 21 # 1.在协程中传统的阻塞模式不要写进协程里面 22 # 2.使用asyncio这种模式之后,前面要加一个await关键字,就是等待asyncio.sleep(2).等待asyncio.sleep(2)执行完成. 23 # 此处必须要加await关键字. 24 # asyncio.sleep(2) 25 await asyncio.sleep(2) 26 print("end get url") 27 28 29 if __name__ == '__main__': 30 start_time = time.time() 31 32 # 关键是怎样使用协程,协程必须搭配事件循环才能使用 33 # 1.获取asyncio中的事件循环 34 # 只需要这样简单的调用,就可以获得之前在面的章节中介绍过的自己写的一个loop. 35 # 就会去完成select的操作. 36 loop = asyncio.get_event_loop() 37 38 # 2.run_until_complete()是一个阻塞的方法. 39 # 将协程作为实参传递进来 40 # 这行语句简单的理解为,之前使用多线程编程模式的join()方法. 41 # run_until_complete()方法会等到协程执行完毕后在会执行print()语句,在这里的话,可以把asyncio理解成协程池 42 loop.run_until_complete(get_html("http://www.baidu.com")) 43 44 print("run time: {}".format(time.time() - start_time)) 45 46 """ 47 输出结果: 48 start get url 49 end get url 50 run time: 2.0021145343780518 51 52 整个代码看起来比自己来实现协程的代码要简单的多. 53 """
async_io_event_loop_02.py
1 """ 2 使用asyncio 3 4 最简单的使用方式来完成协程的一个运行 5 6 使用await time.sleep(2)报错 7 """ 8 9 import asyncio 10 import time 11 12 13 async def get_html(url): 14 """ 15 定义一个协程 16 """ 17 print("start get url") 18 # 模拟html请求过程 19 # 注意:不能使用time.sleep(2). 20 # await asyncio.sleep(2) 21 await time.sleep(2) 22 print("end get url") 23 24 25 if __name__ == '__main__': 26 start_time = time.time() 27 loop = asyncio.get_event_loop() 28 loop.run_until_complete(get_html("http://www.baidu.com")) 29 print("run time: {}".format(time.time() - start_time)) 30 31 """ 32 输出结果:报错 33 await time.sleep(2) 34 TypeError: object NoneType can't be used in 'await' expression 35 36 分析报错原因: 37 object NoneType: 38 因为time.sleep(2)是没有返回值的,所以是NoneType空类型 39 40 await: 41 await后面必须跟一个Awaitable类型的对象.协程是一个Awaitable对象. 42 43 asyncio.sleep(2)的sleep()方法上有协程装饰器: 44 @coroutine 45 def sleep(delay, result=None, *, loop=None): 46 #Coroutine that completes after a given time (in seconds). 47 if delay == 0: 48 yield 49 return result 50 51 if loop is None: 52 loop = events.get_event_loop() 53 future = loop.create_future() 54 h = future._loop.call_later(delay, 55 futures._set_result_unless_cancelled, 56 future, result) 57 try: 58 return (yield from future) 59 finally: 60 h.cancel() 61 """
async_io_event_loop_03.py
1 """ 2 使用asyncio 3 4 最简单的使用方式来完成协程的一个运行 5 """ 6 7 import asyncio 8 import time 9 10 11 async def get_html(url): 12 """ 13 定义一个协程 14 """ 15 print("start get url") 16 # 模拟html请求过程 17 # 注意:不能使用time.sleep(2). 18 # await asyncio.sleep(2) 19 time.sleep(2) 20 print("end get url") 21 22 23 if __name__ == '__main__': 24 start_time = time.time() 25 loop = asyncio.get_event_loop() 26 loop.run_until_complete(get_html("http://www.baidu.com")) 27 print("run time: {}".format(time.time() - start_time)) 28 29 """ 30 使用time.sleep(2)输出结果并不会报错 31 start get url 32 end get url 33 run time: 2.003114700317383 34 """
async_io_event_loop_04.py
1 """ 2 使用time.sleep(2)输出结果并不会报错 3 start get url 4 end get url 5 run time: 2.003114700317383 6 7 那为什么一再强调协程里面不能使用阻塞IO??? 8 为了说明这个问题,假设在一个并发的情况下,比如获取10次url 9 10 采用并发模式 11 """ 12 13 import asyncio 14 import time 15 16 17 async def get_html(url): 18 """ 19 定义一个协程 20 """ 21 print("start get url") 22 # 模拟html请求过程 23 # 注意:不能使用time.sleep(2). 24 # await asyncio.sleep(2)语句会返回一个Future对象,和线程是一样的会立即返回一个url. 25 # 在下一次调用的时候会判断这个时间有没有到,到了就和执行print("end get url")语句 26 await asyncio.sleep(2) 27 print("end get url") 28 29 30 if __name__ == '__main__': 31 start_time = time.time() 32 loop = asyncio.get_event_loop() 33 34 # tasks是一个列表,列表中每一个元素都是协程对象 35 # tasks = [get_html("http://www.baidu.com") for i in range(100)] 36 tasks = [get_html("http://www.baidu.com") for i in range(10)] 37 38 # wait()方法接收一个可迭代对象. 39 # 等待所有任务完成之后才会执行下一步 40 loop.run_until_complete(asyncio.wait(tasks)) 41 print("run time: {}".format(time.time() - start_time)) 42 43 """ 44 输出结果: 瞬间打印,并发性非常的高. 45 start get url 46 start get url * 9 简写打印结果 47 end get url 48 end get url * 9 简写打印结果 49 run time: 2.0041146278381348 50 51 获取100个url也是这样的,用时run time: 2.019115447998047 52 tasks = [get_html("http://www.baidu.com") for i in range(100)] 53 """
async_io_event_loop_05.py
1 """ 2 使用time.sleep(2)输出结果并不会报错 3 start get url 4 end get url 5 run time: 2.003114700317383 6 7 那为什么一再强调协程里面不能使用阻塞IO??? 8 为了说明这个问题,假设在一个并发的情况下,比如获取10次url 9 10 采用time.sleep(2)来完成模拟网络请求. 11 """ 12 13 import asyncio 14 import time 15 16 17 async def get_html(url): 18 """ 19 定义一个协程 20 """ 21 print("start get url") 22 # 模拟html请求过程 23 # 注意:不能使用time.sleep(2). 24 # await asyncio.sleep(2) 25 time.sleep(2) 26 print("end get url") 27 28 29 if __name__ == '__main__': 30 start_time = time.time() 31 loop = asyncio.get_event_loop() 32 33 # tasks是一个列表,列表中每一个元素都是协程对象 34 tasks = [get_html("http://www.baidu.com") for i in range(10)] 35 36 # wait()方法接收一个可迭代对象. 37 # 等待所有任务完成之后才会执行下一步 38 loop.run_until_complete(asyncio.wait(tasks)) 39 print("run time: {}".format(time.time() - start_time)) 40 41 """ 42 输出结果: 2秒钟请求一个url,这样就和同步执行没有什么区别了.执行的很慢 43 start get url 44 end get url 45 start get url 46 end get url 47 ... * 7 48 start get url 49 end get url 50 run time: 20.003144025802612 51 """
********
posted on 2019-05-15 22:03 jaydenjune 阅读(172) 评论(0) 收藏 举报
浙公网安备 33010602011771号