目录
欢迎关注 『python爬虫』 专栏,持续更新中
欢迎关注 『python爬虫』 专栏,持续更新中
1. 初识协程
- 适用场景:任务为 IO 密集型如 web 服务器、网关等频繁输入输出),追求高吞吐
- 为什么不使用进程 / 线程?进程线程的调度涉及到内核态与用户态的信息交换,但是协程可以在用户态就完成调度,省去一部分与内核态的信息交互带来的损耗,不准确的类比是:原本的线程进程用户态-内核态-用户态的交互–>用户态-用户态之间的交互,无疑更加具有效率。
- 大量使用进程线程带来的问题:
系统线程会占用非常多的内存空间
过多的线程切换会占用大量的系统时间。
多线程开发涉及锁、竞争冲突等,开发复杂
解决问题的思路:
- 为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU花在切换上的开销也越大。
- 使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 IO 多路复用让进程或线程不阻塞,省去上下文切换的开销.
思路优缺点:方案1实现简单,但性能不高;方案2性能非常好,但实现起来复杂。
2. 协程用法
同步与异步
同步 :提交任务必须等待任务完成,才能执行下一行
异步 :提交任务不需要等待任务完成,立即执行下一行
假设有f1,f2,f3这三个任务,分别需要用时2,3,4秒执行。(忽略信息交换调度等损耗)
同步用时:2+3+4=9秒
异步用时:4秒
我们为了提高效率一般情况下用异步比较多,会重点介绍异步。
注意事项1:异步操作中出现同步操作会强制将异步操作的函数变成同步。
不能使用同步操作 time.sleep(3)
异步操作中写了同步操作会强制把异步转化为同步,导致变成串行执行
time.sleep(3)
asyncio.sleep(3)
注意事项2:await 挂起的使用
哪怕使用了asyncio.sleep(3)
会提示警告sys:1: RuntimeWarning: coroutine 'func1' was never awaited
必须要最少在协程中使用一个await
使得在执行到这一行代码时允许协程切换工作任务,否则还是会等待变成串行,所以我们这边的等待时间改为await asyncio.sleep(3)
2.1 多任务同步协程
一个任务一个任务串行执行。
import asyncio
import time
async def func1():
print("func1休息前")
await asyncio.sleep(3) # 不能使用同步操作 time.sleep(3),异步操作中写了同步操作会强制把异步转化为同步,导致变成串行
print("func1休息后")
async def func2():
print("func2休息前")
await asyncio.sleep(2)
print("func2休息后")
async def func3():
print("func3休息前")
await asyncio.sleep(4)
print("func3休息后")
if __name__ == '__main__':
f1 = func1()
f2 = func2()
f3 = func3()
tasks = [
f1, f2, f3
]
t1 = time.time()
asyncio.run(f1)
asyncio.run(f2)
asyncio.run(f3)
t2 = time.time()
print(t2 - t1)
2.2 多任务异步协程
- 下面是python 3.8 及之前老版本的写法,新版本写法在后文
import asyncio
import time
async def func1():
print("func1休息前")
await asyncio.sleep(3)#不能使用同步操作 time.sleep(3),异步操作中写了同步操作会强制把异步转化为同步,导致变成串行
print("func1休息后")
async def func2():
print("func2休息前")
await asyncio.sleep(2)
print("func2休息后")
async def func3():
print("func3休息前")
await asyncio.sleep(4)
print("func3休息后")
if __name__ == '__main__':
f1 = func1()
f2 = func2()
f3 = func3()
tasks = [
f1, f2, f3
]
t1 = time.time()
# 一次性启动多个任务(协程)
asyncio.run(asyncio.wait(tasks))
t2 = time.time()
print(t2 - t1)
- python 3.11 中的写法
import asyncio
import time
async def func1():
print("func1休息前")
await asyncio.sleep(3)
print("func1休息后")
async def func2():
print("func2休息前")
await asyncio.sleep(2)
print("func2休息后")
async def func3():
print("func3休息前")
await asyncio.sleep(4)
print("func3休息后")
async def main():
# 第一种写法
# f1 = func1()
# await f1 # 一般await挂起操作放在协程对象前面
# 第二种写法(推荐)
tasks = [
asyncio.create_task(func1()),
# py3.8以后加上asyncio.create_task()
asyncio.create_task(func2()),
asyncio.create_task(func3())
]
await asyncio.wait(tasks)
if __name__ == '__main__':
t1 = time.time()
# 一次性启动多个任务(协程)
asyncio.run(main())
t2 = time.time()
print(t2 - t1)
3. 爬虫异步协程框架
import asyncio
# 在爬虫领域的应用
async def download(url):
print("准备开始下载"+url)
#我们这里就只是打印一下,没有把爬虫的代码放进这里
#这个位置写爬虫相关的代码,或者是你其他任务的代码
await asyncio.sleep(2) # 网络请求 requests.get()
print("下载完成"+url)
async def main():
urls = [
"http://www.baidu.com",
"http://www.bilibili.com",
"http://www.163.com"
]
# 准备异步协程对象列表
tasks = []
for url in urls:
d = asyncio.create_task(download(url))
tasks.append(d)
# tasks = [asyncio.create_task(download(url)) for url in urls] # 这么干也行哦~
# 一次性把所有任务都执行
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
总结
大家喜欢的话,给个👍,点个关注!给大家分享更多计算机专业学生的求学之路!
版权声明:
发现你走远了@mzh原创作品,转载必须标注原文链接
Copyright 2023 mzh
Crated:2023-3-1
欢迎关注 『python爬虫』 专栏,持续更新中
欢迎关注 『python爬虫』 专栏,持续更新中
『未完待续』