python协程使用场景_python 的协程和异步

刚进公司,发现公司的后台程序是基于异步和协程的,而我之前一直在用同步的方式写 web 后台,所以就花两天时间 google 了几乎所有的讲解文章,总结成此文。本文特点有:适合不懂协程的人建立一些基本的概念,但是没有进阶内容。

需要一些爬虫的基本概念。

会用范例程序来解释相对难懂的概念。

由于作者刚接触这种编程方法,所以难免会有一些错漏,欢迎指出。

协程

先抛出协程的概念: 协程就是一个函数,只是它满足以下几个特征:有 I/O 依赖的操作。

可以在进行 I/O 操作时暂停。

无法直接执行。

它的作用就是对有大量 I/O 操作的程序进行加速。

来看个例子:众所周知一个简单的爬虫可以分为三个步骤:1、构造 URL;2、发送请求获取响应;3、从响应获取数据。在第二步的时候,CPU 其实是处于闲置状态的,我们称之为「阻塞状态」。假设我们要爬取一百个网页,那么就要进入一百次阻塞状态,这就是整个程序的性能瓶颈。我们当然希望在阻塞状态时可以把 CPU 利用起来。

那么如何改进呢?这就要引入「非阻塞」和「异步」了。之前在第二步时,用一种编程方法,在执行爬取 A 网页的第二步时可以先去执行 B 网页的爬取程序,也就是说对于 A 网页的爬取程序,并不是遵循 1》2》3 的步骤进行的,这就是「异步」,反之则为「同步」。异步可以让程序进入「非阻塞」状态。

执行单个协程没有给程序加速的效果,而为了执行多个协程,我们需要一个「工具」去调度它们,这个工具就是「事件循环」。这里又涉及到 future 和 Task,future 指的是保证可以在未来执行并得到结果的一段程序,当 future 里包含协程时就成为了一个 task,在 python 的协程里我们可以把 future 和 task 当做一个概念。当调用了相应的 api 以后,我们可以用一个协程生成一个 task,然后就可以在事件循环中调度,当 A task 执行 I/O 操作时,事件循环就会执行 B task,当 A task 执行完 I/O 操作时,它就会恢复执行 A task。

先用同步编写一个爬虫:

import time

import requests

def get_response(arg):

r = requests.get(arg[1])

print('task {}'.format(arg[0]))

if __name__ == '__main__':

urls = [('A', 'https://movie.douban.com/top250'), ('B', 'https://movie.douban.com/top250?start=25&filter='),

('C', 'https://movie.douban.com/top250?start=50&filter=')]

start = time.time()

for u in urls:

get_response(u)

end = time.time()

print('running time', end - start)

显示结果是:

task A

task B

task C

running time 1.2088820934295654

可以清楚地看到它的执行顺序,以及运行时间。

再用异步编写一个爬虫:

import time

import asyncio

import aiohttp

async def get_response(arg):

print(arg[0])

async with aiohttp.ClientSession() as session:

async with session.get(arg[1]) as resp:

pass

if __name__ == '__main__':

urls = [('A', 'https://movie.douban.com/top250'),

('B', 'https://movie.douban.com/top250?start=25&filter='),

('C', 'https://movie.douban.com/top250?start=50&filter=')]

loop = asyncio.get_event_loop()

tasks = [asyncio.ensure_future(get_response(urls[0])),

asyncio.ensure_future(get_response(urls[1])),

asyncio.ensure_future(get_response(urls[2]))

]

start = time.time()

loop.run_until_complete(asyncio.wait(tasks))

end = time.time()

print('running time', end - start)

显示结果是:

task A

task B

task C

running time 0.4606208801269531

可以看到运行速度快了三倍左右。

线程和协程

正如线程是对进程的划分,协程又是对线程的划分。事件循环里的 tasks 都是跑在同一个线程里的,这也是我写爬虫时没有用 requests 的原因,因为它是线程阻塞的。

生成器

相信熟悉生成器的读者在看到之前描述协程特征的「暂停」二字就想到了生成器,不熟悉也没有关系。

生成器与协程有着很强的关联,他们都是利用让程序暂停的思路来对程序做优化,只是协程是针对速度,生成器是针对内存。

先假设这样一个场景:你的 CPU 内存很小,但是想要它打印出一亿个数,怎么实现?首先列表是不可行的,因为内存不够用,这就引出生成器这样一个工具:它是基于运算规律生成的一个算式,每次只能返回一个值。这个值可以用 next() 或者 for...in...来获取。

def infinite():

i = 0

while True:

yield i

i += 1

if __name__ == '__main__':

for i in infinite():

print(i)

这个程序可以把递增的整数一直打印下去,直到地老天荒。

它的内部原理是每次运行到 yield,程序就会暂停,在下次调用时会接着上一次调用时暂停的指令,继续执行。

在 web 开发中的应用场景

1、基于协程的框架 sanic sanic api 非常接近于 flask,但是它会在同一个进程内基于协程对不同的请求做处理。

2、大量的 I/O 操作 比如某个函数需要从多家供应商那里调取数据,那么就可以把调取某家供应商的函数当做 task,用事件循环对这些 tasks 进行调度。

可以感受到,协程和异步其实是很优美的思路,没有坚深的数学公式,也没有消耗额外的 CPU 资源,就轻易地提升了程序的性能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值