『python爬虫』18. 协程(保姆级图文)


欢迎关注 『python爬虫』 专栏,持续更新中
欢迎关注 『python爬虫』 专栏,持续更新中

1. 初识协程

  • 适用场景:任务为 IO 密集型如 web 服务器、网关等频繁输入输出),追求高吞吐
  • 为什么不使用进程 / 线程?进程线程的调度涉及到内核态与用户态的信息交换,但是协程可以在用户态就完成调度,省去一部分与内核态的信息交互带来的损耗,不准确的类比是:原本的线程进程用户态-内核态-用户态的交互–>用户态-用户态之间的交互,无疑更加具有效率。
  • 大量使用进程线程带来的问题:
    系统线程会占用非常多的内存空间
    过多的线程切换会占用大量的系统时间。
    多线程开发涉及锁、竞争冲突等,开发复杂

解决问题的思路:

  1. 为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU花在切换上的开销也越大。
  2. 使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 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爬虫』 专栏,持续更新中
『未完待续』


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发现你走远了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值