python3 asyncio_python3.7中asyncio的具体实现

异步IO 协程 是写爬虫目前来说最好的方式.

比多线程和多进程都好. 开辟新的线程和进程是非常耗时的

讲讲我在使用python异步IO语法时踩过的坑

简单介绍异步IO的原理

以及利用最新语法糖实现异步IO的步骤,

然后给出实现异步的不同例子

网上找了很多python的asyncio示例.很多都是用

#获取EventLoop:

loop =asyncio.get_event_loop()#执行coroutine

loop.run_until_complete(hello())

loop.close()

通过create_future向里面添加task的方法来进行异步IO调用.

这种方法显然不是很好理解,在python3.7中 asyncio引入了新的语法糖

asyncio.run()

asyncio.create_task()

asyncio.gather()

下面通过实例具体分析asyncio异步的原理和使用方法

假设有一个异步操作, 它可以是爬虫的请求等待网页响应, 数据库的操作, 或者是定时任务. 不管如何, 我们都可以抽象成下面这个函数来表示

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

/*

预期想要的结果

----start foo

(等待一秒)

----end foo

*/

async是旧版本装饰器的语法糖

await是旧版本yield from 的语法糖

这个函数表示,先打印start foo 然后等待一秒, 然后再打印end foo

这个函数不能直接被执行. 它必须放在一个异步环境中才能执行. 这个异步环境独立在整个程序之外,可以把所有的异步环境打包成一个箱子, 看成是一个同步事件.

(异步环境是我自己创造的为了理解异步操作发明的词汇)

把这个函数装在这个异步环境里 异步环境的长度取决于环境里需要执行事件最长的那个函数

开启这个异步环境的标志是

asyncio.run(foo())

这条命令执行了之后,异步环境就被开启了. 需要主要的事, 同一线程同一时间只能开启一个异步环境. 换句话说, 在run函数里面的函数(本例中为bar())里面不能再包含run函数.

因此, 上例需要执行的话:

async deffoo():print('start foo')

await asyncio.sleep(1)print('----end foo')if __name__ == '__main__':

asyncio.run(foo())

执行以下之后发现结果没问题

异步是为了处理IO密集型事件的.一个读取操作需要1秒, 另一个需要2秒, 如果并发执行,需要3秒,

deffoo2():print('----start foo')

time.sleep(1)print('----end foo')defbar2():print('----start bar')

time.sleep(2)print('----end bar')if __name__ == '__main__':

foo2()

bar2()/*预期输出:----start foo

(等待1秒)----end foo----start bar

(等待2秒)----end bar*/

把上面的函数改写成异步之后

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdefmain():

await foo()

await bar()if __name__ == '__main__':

asyncio.run(main())

我们想要的结果是

----start foo

****start bar

(等待1秒)

----end foo

(等待1秒)

****end bar

但是运行上面的程序 结果却是

----start foo

(等待1秒)

----end foo

****start bar

(等待2秒)

****end bar

这是为什么呢

await表示 等待后面的异步函数操作完了之后, 执行下面的语句.

所以在在本例中,await foo 等待foo函数完全结束了之后, 再去执行

那么如何一起执行呢

基本的有两种方法

1.采用函数gather

官方文档中的解释是

awaitableasyncio.gather(*aws, loop=None, return_exceptions=False)

并发 运行 aws 序列中的 可等待对象。

如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。

如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。

如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。

如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。

如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消。

如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 -- 在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。

因此代码就有了

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdefmain():

res=await asyncio.gather(foo(), bar())print(res)if __name__ == '__main__':

asyncio.run(main())

返回值为函数的返回值列表 本例中为[None, None]

第二种方法 创建task

asyncio.create_task(coro)

将 coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。

该任务会在 get_running_loop() 返回的loop中执行,如果当前线程没有在运行的loop则会引发 RuntimeError。

此函数 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdefmain():asyncio.create_task(foo())asyncio.create_task(bar())if __name__ == '__main__':

asyncio.run(main())

但是运行一下就会发现, 只输出了

----start foo

****start bar

这是因为,create_task函数只是把任务打包放进了队列,至于它们有没有运行完. 不管.

因此需要等待它们执行完毕.

最后的代码为

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdefmain():

task1=asyncio.create_task(foo())

task2=asyncio.create_task(bar())

await task1

await task2if __name__ == '__main__':

asyncio.run(main())

如果有多个请求

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefmain():

tasks=[]for i in range(10):

tasks.append(asyncio.create_task(foo()))

await asyncio.wait(tasks)if __name__ == '__main__':

asyncio.run(main())

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdefmain():

tasks=[]for i in range(10):

tasks.append(asyncio.create_task(foo()))for j in range(10):

tasks.append(asyncio.create_task(bar()))

await asyncio.wait(tasks)if __name__ == '__main__':

asyncio.run(main())

异步嵌套

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdefbar():print('****start bar')

await asyncio.sleep(2)print('****end bar')

asyncdeffoos():print('----------------------')

tasks=[]for i in range(3):

tasks.append(asyncio.create_task(foo()))

await asyncio.wait(tasks)

asyncdefmain():

tasks=[]for i in range(3):

tasks.append(asyncio.create_task(foos()))for j in range(3):

tasks.append(asyncio.create_task(bar()))

await asyncio.wait(tasks)if __name__ == '__main__':

asyncio.run(main())

把每一个create_task当成新增了一条线. 这条线如果遇到IO操作了(即遇到了await) 那么就先等待在这里, 先执行别的线上的操作(如果已经有了结果)

create了线才可以跳来跳去, 如果不create, 是不会跳走的

async deffoo():print('----start foo')

await asyncio.sleep(1)print('----end foo')

asyncdeffoos():print('----------------------')

tasks=[]

await foo()

await foo()

await foo()

asyncdefmain():

tasks=[]for i in range(3):

tasks.append(asyncio.create_task(foos()))

await asyncio.wait(tasks)if __name__ == '__main__':

asyncio.run(main())

这个例子里面 只创造了3条线, 因此只能3个3个执行, 其实应该9个一起等, 但是因为没有create_task所以并不会一起执行.

importasyncioimportaiohttp

asyncdeffetch(session, url, sem):

timeout= aiohttp.ClientTimeout(total=2)try:

async with sem:print(f'start get: {url}')

async with session.get(url, timeout=timeout) as response:

res=await response.text()print(f'get {url} successfully')except:print('timeout')

asyncdefmain():

url_list=[#'https://www.google.com.hk/',

'https://www.cnblogs.com/DjangoBlog/p/5783125.html','http://www.360doc.com/content/18/0614/19/3175779_762447601.shtml','https://www.baidu.com/',

]

url_list2= ['http://es6.ruanyifeng.com/#docs/decorator' for _ in range(100)]

url_list3= ['https://www.baidu.com' for _ in range(100)]#async with aiohttp.ClientSession() as session:

#tasks = []

#sem = asyncio.Semaphore(20)

#for url in url_list3:

#tasks.append(fetch(session, url, sem))

#await asyncio.gather(*tasks)

async with aiohttp.ClientSession() as session:

sem= asyncio.Semaphore(20)

url= 'https://www.baidu.com'task_list= [fetch(session, url, sem) for _ in range(100)]

await asyncio.gather(*task_list)if __name__ == '__main__':

asyncio.run(main())

被注释掉的代码和下面的代码干的是同样的事

Semaphore是一个计数器 超过容量的时候会阻塞. 可以限制并发数量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值