协程

  在python3.5以前,写成的实现都是通过生成器的yield from原理实现的, 这样实现的缺点是代码看起来会很乱,于是3.5版本之后python实现了原生的协程,并且引入了async和await两个关键字用于支持协程。于是在用async定义的协程与python的生成器彻底分开。

async def downloader(url):
return 'bobby' async def download_url(url):
html = await downloader(url)
return html
if __name__ == '__main__':
coro = download_url('http://www/imooc.com')
coro.send(None) 输出结果:
Traceback (most recent call last):
File "D:/MyCode/Cuiqingcai/Flask/test01.py", line , in <module>
coro.send(None)
StopIteration: bobby

可以看到结果中可以将downloader(url)的结果返回。需要注意的是在原生协程里面不能用next()来预激协程。

async def downloader(url):
return 'bobby' async def download_url(url):
html = await downloader(url)
return html
if __name__ == '__main__':
coro = download_url('http://www/imooc.com')
coro.next() 结果:
AttributeError: 'coroutine' object has no attribute 'next'
sys:1: RuntimeWarning: coroutine 'download_url' was never awaited

原生协程async代码中间是不能在使用yield生成器的,这样就为了更好的将原生协程与生成器严格区分开来。并且await只能和async语句搭配,不能和生成器搭配。因为要调用await需要调用对象实现__await__()这个魔法方法。所以在定义协程时候注意不要混用。但是理解的时候还是可以将原生的协程中 await可以对比生成器的yield from。

asyncio

高并发的核心模块,3.4之后引入,最具野性的模块,web服务器,爬虫都可以胜任。它是一个模块也可以看做一个框架。

协程编码模式的三个要点:

  1. 事件循环
  2. 回调(驱动生成器(协程))
  3. epoll(IO多路复用)

  asyncio的简单实用:

  这里需要注意的是:同步阻塞的接口不能使用在协程里面。因为协程是单线程的,只要有一个地方阻塞了,那么所有的协程都需要等待阻塞结束之后才可以向下运行,于是在协程函数中等待一定不能用time.sleep() 如果用time.sleep()就失去了协程的意义了(即程序运行的时间将会是每个协程数乘以time.sleep()的时间数。)同时用asyncio.sleep() 之前需要加上 await

import time
async def download_url(url):
print('start get %s' % url)
await asyncio.sleep(2)
print('get %s finished.' % url)
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(download_url('https:www.baidu.com')) # 阻塞等事件完成之后再向下运行。相当于进程线程的join()方法,或者进线程池的wait()
print('一共用时:%s' % (time.time() - start_time)) start get https:www.baidu.com
get https:www.baidu.com finished.
一共用时:2.0011143684387207

  一次执行多个任务。(用时和一个任务一样!)下面任务如果换成time.sleep(2)则函数需要至少20秒才能执行完毕。

import asyncio
import time
async def download_url(url):
print('start get %s.' % url)
await asyncio.sleep(2)
print('get %s end' % url) if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
url_list = []
for i in range(10):
url_list.append('https:www.baidu.com.index{}'.format(i))
tasks = [download_url(url) for url in url_list]
loop.run_until_complete(asyncio.wait(tasks))
print('用时 %s' %(time.time() - start_time)) ...
...
用时 2.0031144618988037

  获取协程的返回值:

  可以用两种方式先得到一个future对象。然后将该future对象放入loop.run_until_complete()中。(该函数即可以接受future对象也可以接受协程对象)然后future对象跟进程池和线程池中的future对象的方法是一样的。于是可以用.result()方法的到函数的返回结果。

async def download_url(url):
# print('start get %s' % url)
await asyncio.sleep(2)
# print('get %s finished.' % url)
return 'frank is a good man.'
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
# get_future = asyncio.ensure_future(download_url('https:www.baidu.com'))
get_task = loop.create_task(download_url('https:www.baidu.com'))
loop.run_until_complete(download_url(get_task))
print('一共用时:%s' % (time.time() - start_time))
print(get_task.result()) 一共用时:2.0021142959594727
frank is a good man.

  future完成之后的回调函数

  设置协程完成之后的回调函数:future对象的.add_done_callback(),注意在调用add_done_callback的时候会默认将future对象传递给回调函数,因此回调函数必须至少接受一个参数,同时add_done_callback必须在run_until_complete()前,(协程函数创建之后调用)因为如果在run_until_complete()之后的话,协程都应结束了。就不会起作用了。

async def download_url(url):
# print('start get %s' % url)
await asyncio.sleep(2)
# print('get %s finished.' % url)
return 'frank is a good man.' def send_emai(future):
print('网页下载完毕')
print(future.result()) if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
# get_future = asyncio.ensure_future(download_url('https:www.baidu.com'))
get_task = loop.create_task(download_url('https:www.baidu.com'))
get_task.add_done_callback(send_emai)
loop.run_until_complete(download_url(get_task)) print('一共用时:%s' % (time.time() - start_time))
print(get_task.result()) 网页下载完毕
frank is a good man.
一共用时:2.0021145343780518
frank is a good man.

问题来了。tasks.add_done_callback方法只能接受函数名,如果回调的方法也需要参数怎么办?这就需要用到偏函数from functools import partial (偏函数可以将函数包装成为另外一个函数)

import  asyncio
import time
from functools import partial
async def get_html(url):
print('start get url')
await asyncio.sleep(2)
return 'bobby' def callback_method(url, future):# 此处因为是future对象即(tasks)调用的,所以有一个默认的参数。同时要注意:回调函数的future只能放到最后,其它的函数实参放前面。
print('download ended %s' % url) if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
tasks = asyncio.ensure_future(get_html('http://www.baidu.com'))
# tasks = loop.create_task(get_html('http://www.baidu.com'))
tasks.add_done_callback(partial(callback_method, 'http://www.baidu.com')) # 参数为回调方法
loop.run_until_complete(tasks)
print(tasks.result()) start get url
download ended http://www.baidu.com
bobby

gather 与 wait的区别。都是可以等待程序运行之后往下运行。但是gather比wait更加高级一点

gather可以将任务分组:

import  asyncio
import time async def get_html(url):
print('start get %s' % url)
await asyncio.sleep(2)
print('end get %s' % url)
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
group1 = [get_html('http://goup1.com') for i in range(2)]
group2 = [get_html('http://group2.com') for i in range(2)]
group1 = asyncio.gather(*group1)
group2 = asyncio.gather(*group2)
loop.run_until_complete(asyncio.gather(group1, group2))
# 上面三行代码也可以合一起loop.run_until_complete(asyncio.gather(*group1, *group2)) 注意此参数前面要加*
print(time.time()-start_time) start get http://goup1.com
start get http://group2.com
start get http://group2.com
start get http://goup1.com
end get http://goup1.com
end get http://group2.com
end get http://group2.com
end get http://goup1.com
2.0021145343780518

loop.run_forever()协程的任务完成之后不会停止,而是会一直运行。老师吐槽:python中间loop和future的关系有点乱,loop会被放到future中间同时future又可以放到loop中间,造成一个循环。

如何取消future(task)

async def get_html(sleep_time):
print('waiting')
await asyncio.sleep(sleep_time)
print('end after %s S' % sleep_time) if __name__ == '__main__':
task1 = get_html(2)
task2 = get_html(3)
task3 = get_html(4)
tasks = [task1, task2, task3]
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
all_tasks = asyncio.Task.all_tasks()
for task in all_tasks:
print('cancel task')
print(task.cancel())# 打印是否取消 是返回Ture,否False
loop.stop()
loop.run_forever() # 注意此处一定要加上loop.run_forever()不然会报异常
finally:
loop.close()

waiting
waiting
waiting
cancel task
True
cancel task
True
cancel task
True
cancel task
True

将此代码进入cmd中运行,然后再中间按ctrl + C键,主动生成一个 KeyboardInterrupt 异常,然后异常被捕捉之后做出处理(即停止协程的运行)

协程的嵌套

import asyncio
async def compute(x, y):
print('Computer %s + %s...') % (x, y))
await asyncio.sleep(1)
return x + y async def print_sum(x, y):
result = await computer(x, y)
print("%s + %s = %s" % (x, y, result)) loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

代码分析图:

  1. loop首先会为print_sum()创建一个task
  2. event_loop()驱动task运行,使task进入(pending状态)
  3. task去执行print_sum
  4. print_sum中首先进入子协程的调度(await相当于yiel from)所以转向执行computer,print_sum自身暂停。
  5. compute中存在await 于是也被迫进入暂停状态,然后可以直接返回给task(await == yield from 而yield from可以在调度方与子生成器之间掠过委托方建立双向通道)
  6. task返回给event_loop()
  7. 等待1秒钟之后,task唤醒compute,compute继续执行下一行代码(即return x + y)完成之后compute就是一个done状态,同时抛出一个stopiterationError异常,此异常将激活print_sum()(委托方),并且将异常将被await(对应之前的yield from)捕捉并提取出return的值。
  8. print_sum()被激活之后执行print然后变成done状态,也会抛出一个stopiterationError异常,然后被task接受并处理了。

asyncio中的其他函数(以下三个为底层函数,多数条件下用得不多)

call_soon()

  在协程运行时候,可以传递一些函数去执行,注意是函数不是协程。

import asyncio
def callback(sleep_times):
print('sleep {} success'.format(sleep_times)) if __name__ =='__main__':
loop = asyncio.get_event_loop()
loop.call_soon(callback, ) #第一个参数为调用的函数名,第二个单数为被调用函数的参数
loop.run_forever() sleep success

  注意此处调动函数的运行需要用到loop.run_forever()而不是loop.run_until_complete()因为oop.call_soon()只是调用函数而不是loop注册的协程。

同时loop.run_forever()会导致程序一直在运行,不会自动结束。于是需要添加以下方法使程序关闭。

import asyncio
def callback(sleep_times):
print('sleep {} success'.format(sleep_times)) def stop_loop(loop):
loop.stop() if __name__ =='__main__':
loop = asyncio.get_event_loop()
loop.call_soon(callback, 2)
loop.call_soon(stop_loop, loop)
loop.run_forever()

call_later()

  功能,讲一个callback函数在一个指定的时候运行。

import asyncio
def callback(sleep_times):
print('sleep {} success'.format(sleep_times)) def stop_loop(loop):
loop.stop() if __name__ =='__main__':
loop = asyncio.get_event_loop()
loop.call_later(2, callback, 2) # 参数含义:第一个参数为延迟时间,延迟越少越先运行。
loop.call_later(1, callback, 1)
loop.call_later(3, callback, 3)
loop.call_soon(callback, 4)
loop.run_forever() sleep 4 success
sleep 1 success
sleep 2 success
sleep 3 success

同时存在call_soon()和call_later()时,call_soon()会在那个call_later()前面调用。

call_at()

  也是调用函数在指定时间内调用,但是它的指定时间,是指的loop内的时间,而不是自己传递的时间。可以用loop.time()来获取loop内时间。

import asyncio
def callback(sleep_times):
print('sleep {} success'.format(sleep_times)) if __name__ =='__main__':
loop = asyncio.get_event_loop()
now = loop.time()
loop.call_at(now+2, callback, 2) # 注意第一个参数是相对于loop系统时间而来的,不是自定义的几秒钟之后运行。
loop.call_at(now+1, callback, 1)
loop.call_at(now+3, callback, 3)
loop.call_soon(callback, 4)
loop.run_forever() sleep 4 success
sleep 1 success
sleep 2 success
sleep 3 success

协程中线程的实现

协程不提供阻塞的方式,但是有时候有些库,和有些接口只能提供阻塞方式连接。于是就可以在协程中继承阻塞io

Python 原生协程------asyncio的更多相关文章

  1. 运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践

    我们一直都相信这样一种说法:协程是比多线程更高效的一种并发工作方式,它完全由程序本身所控制,也就是在用户态执行,协程避免了像线程切换那样产生的上下文切换,在性能方面得到了很大的提升.毫无疑问,这是颠扑 ...

  2. 小议Python3的原生协程机制

    此文已由作者张耕源授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 在最近发布的 Python 3.5 版本中,官方正式引入了 async/await关键字.在 asyncio ...

  3. 物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_221 一般情况下,大家对Python原生的并发/并行工作方式:进程.线程和协程的关系与区别都能讲清楚.甚至具体的对象名称.内置方法 ...

  4. Python3的原生协程(Async/Await)和Tornado异步非阻塞

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_113 我们知道在程序在执行 IO 密集型任务的时候,程序会因为等待 IO 而阻塞,而协程作为一种用户态的轻量级线程,可以帮我们解决 ...

  5. 深入理解Python中协程的应用机制: 使用纯Python来实现一个操作系统吧!!

    本文参考:http://www.dabeaz.com/coroutines/   作者:David Beazley 缘起: 本人最近在学习python的协程.偶然发现了David Beazley的co ...

  6. 关于Python的协程问题总结

    协程其实就是可以由程序自主控制的线程 在python里主要由yield 和yield from 控制,可以通过生成者消费者例子来理解协程 利用yield from 向生成器(协程)传送数据# 传统的生 ...

  7. 协程,greenlet原生协程库, gevent库

    协程简介 协程(coroutine),又称为微线程,纤程,是一种用户级的轻量级线程.协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来时,恢复之前保存的上下文 ...

  8. python gevent 协程

    简介 没有切换开销.因为子程序切换不是线程切换,而是由程序自身控制,没有线程切换的开销,因此执行效率高, 不需要锁机制.因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断 ...

  9. {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二

    python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...

随机推荐

  1. 借助Chrome和插件爬取数据

    工具 Chrome浏览器 TamperMonkey ReRes Chrome浏览器 chrome浏览器是目前最受欢迎的浏览器,没有之一,它兼容大部分的w3c标准和ecma标准,对于前端工程师在开发过程 ...

  2. 使用 Moq 测试.NET Core 应用 - Why Moq?

    什么是Mock 当对代码进行测试的时候, 我们经常需要用到一些模拟(mock)技术. 绿色的是需要被测试的类, 黄色是它的依赖项, 灰色的无关的类 在一个项目里, 我们经常需要把某一部分程序独立出来以 ...

  3. BannerDemo【图片轮播图控件】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 这里简单记录下一个开源库youth5201314/banner的运用.具体用法请阅读<youth5201314/banner& ...

  4. [开源]Entity Framework 6 Repository 一种实现方式

    在使用Entity Framework这种ORM框架得时候,一般结合Repository仓储形式来处理业务逻辑:虽然这种模式带来很多好处,但是也会引发一些争议,在此抛开不谈,小弟结合项目经验来实现一下 ...

  5. 如何使用.net开发一款小而美的O2O移动应用? ——“家庭小秘”APP介绍及采访记录

    “家庭小秘”是一款“互联网+生活服务”平台,为市民家庭提供优质家庭生活服务和企业后勤服务,包含了用户注册.购买预约.订单查询.充值付款.即时通讯等功能. 这款应用已上线至AppStore和安卓的应用商 ...

  6. spring的理解

    看过<fate系列>的博友知道,这是一个七位英灵的圣杯争夺战争.今天主要来谈谈圣杯的容器概念,以便对spring的理解. 圣杯: 圣杯本身是没有实体的,而是将具有魔术回路的存在(人)作为“ ...

  7. Spring中关于AOP的实践之概念

    一.什么是AOP AOP:也称作面向切面编程 在分享几个概念执行我想先举个栗子(可能例子举得并不是特别恰当): 1.假如路人A走在大街上,被一群坏人绑架了: 2.警察叔叔接到报警迅速展开行动:收集情报 ...

  8. 责任链模式 职责链模式 Chain of Responsibility Pattern 行为型 设计模式(十七)

    责任链模式(Chain of Responsibility Pattern) 职责链模式 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系 将这些对象连接成一条链,并沿着这 ...

  9. vue click事件 v-on:click

    v-on:click <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  10. 使用Boostrap框架写一个登录\注册界面

    Bootstrap是一个Web前端开发框架,使用它提供的css.js文件可以简单.方便地美化HTML控件.一般情况下,对控件的美化需要我们自己编写css代码,并通过标签选择器.类选择器.ID选择器为指 ...