爬虫15——协程异步

1、协程,异步的概念

1.1 首先导入一段代码

import time
def func():
    print("我爱黎明!")
    time.sleep(3) #睡三秒,让当前线程处于堵塞状态。
    print("我真的爱黎明")
if __name__ == '__main__':
    a = time.time()
    func()
    b = time.time()
    print(b - a)

通过对运行结果来看,程序一系列完成下来至少要三秒钟的时间。

我爱黎明!
我真的爱黎明
3.0042407512664795

1.2 而在三秒多的时间里,其实有三秒钟是没有工作的,程序进入睡眠状态,在程序睡眠的期间,cpu是不为我们程序所工作的。

例如:生活中你去洗脚,当你正处于泡脚的时候,技师不能为你按摩,先去做别的事情,当你泡完脚后,在来为你按摩。

1.3 思考下我们写程序过程中,有没有什么其他语句也能让程序进入堵塞状态?

# 1. input()语句
# 输入语句 他执行的时候,会让程序停留在终端,等待用户输入,什么时候输入完,再继续往下走

# 2.requests.get(url)
# 当我对url发送请求之后,在网络进行传输,并且url还要准备数据,再传送回来,这个过程我们需要等待服务器那边给我们传输数据,所以也是堵塞状态

#所以总结:
# 一般情况,当程序处于I/O操作的时候(输入,输出),线程会处于堵塞状态

1.4 在程序的堵塞状态下,这个时候就需要用到协程操作了。

协程:当程序遇见了I/O操作的时候,可以选择性的切换到其他任务上,充分的利用CPU。
在微观上是一个任务一个任务的进行切换,切换条件一般都是IO操作
在宏观上,我们能看到的其实是多个任务一起在执行
# 简称 多任务异步操作

根据上一个例子,当你在泡脚的时候,技师不能为你按摩,则你可以让她帮你揉揉肩之类的,等到泡脚结束,再继续按摩。

上方所讲的一切,都是在单线程的条件下 

2、多任务异步协程

2.1 首先简单了解异步协程操作

import asyncio # 导入协程模块

async def func():
    print("我真的爱黎明")
if __name__ == '__main__':
    g = func() # 此时的函数是异步协程函数,
    # print(g) # 此时函数执行得到的是一个协程对象
    asyncio.run(g) # 协程程序运行需要asyncio模块的支持

当只有一个任务的时候,与之前写的没什么太大区别

扫描二维码关注公众号,回复: 16307271 查看本文章

2.2 此时引入多任务异步协程

先引入一段同步的操作

import time
def func():
    print("你好啊,我叫赛利亚")
    time.sleep(3) # 增加耗时操作
    print("你好啊,我叫赛利亚")
def func1():
    print("你好啊,我叫张东墙")
    time.sleep(2)
    print("你好啊,我叫张东墙")
def func2():
    print("你好啊,我叫王伟")
    time.sleep(4)
    print("你好啊,我叫王伟")

if __name__ == '__main__':
    a=time.time()
    func()
    func1()
    func2()
    b = time.time()
    print(b-a,'s')

同步的话,就是程序一个接一个的进行操作,通过观察运行结果可以发现,程序跑了9秒多,其中9秒是程序睡觉的总时长。

/Users/wangminjie/Desktop/爬虫/venv/bin/python /Users/wangminjie/Desktop/爬虫/线程进程/协程.py
你好啊,我叫赛利亚
你好啊,我叫赛利亚
你好啊,我叫张东墙
你好啊,我叫张东墙
你好啊,我叫王伟
你好啊,我叫王伟
9.008739948272705 s

2.3 下面通过多任务异步协程的方式来进行编写

import asyncio
import time
async def func():
    print("你好啊,我叫赛利亚")
    time.sleep(3) # 增加耗时操作
    print("你好啊,我叫赛利亚")
async def func1():
    print("你好啊,我叫张东墙")
    time.sleep(2)
    print("你好啊,我叫张东墙")
async def func2():
    print("你好啊,我叫王伟")
    time.sleep(4)
    print("你好啊,我叫王伟")

if __name__ == '__main__':
    a = time.time()
    p1 = func()
    p2 = func1()
    p3 = func2()
    tasks = [p1, p2, p3]
    # 一次性启动多个任务(协程)
    #多个任务的话,就得交给asyncio.wait来处理
    asyncio.run(asyncio.wait(tasks))
    b = time.time()
    print(b - a, 's')

通过运行结果可以发现,此时的异步操作和上面的同步操作的运行时间差不多

2.4 原因在于,time.sleep()  ,这个是一个同步操作,当程序出现了同步操作当时候,异步就会中断,此时需要将程序睡觉的操作改写为异步的操作asyncio.sleep即可,并用await挂起,当挂起的时候,程序就会切换到下一步进行操作。

import asyncio
import time
async def func():
    print("你好啊,我叫赛利亚")
    # 当程序出现了同步操作的时候,异步就中断了
    #time.sleep(3) # 增加耗时操作
    # await 挂起
    await asyncio.sleep(3) # 异步操作的代码
    print("你好啊,我叫赛利亚")
async def func1():
    print("你好啊,我叫张东墙")
    #time.sleep(2)
    await asyncio.sleep(2)
    print("你好啊,我叫张东墙")
async def func2():
    print("你好啊,我叫王伟")
    #time.sleep(4)
    await asyncio.sleep(4)
    print("你好啊,我叫王伟")

if __name__ == '__main__':
    a = time.time()
    p1 = func()
    p2 = func1()
    p3 = func2()
    tasks = [p1, p2, p3]
    # 一次性启动多个任务(协程)
    #多个任务的话,就得交给asyncio.wait来处理
    asyncio.run(asyncio.wait(tasks))
    b = time.time()
    print(b - a, 's')

这样,就实现了多任务的异步协程操作,整体所用时间是程序中耗时最长的那一段时间加上多任务切换的时间。 

/Users/wangminjie/Desktop/爬虫/venv/bin/python /Users/wangminjie/Desktop/爬虫/线程进程/协程.py
你好啊,我叫王伟
你好啊,我叫赛利亚
你好啊,我叫张东墙
你好啊,我叫张东墙
你好啊,我叫赛利亚
你好啊,我叫王伟
4.005831956863403 s

2.5 但是这样写多任务协程的话,会让主函数的的操作居多,所以需要换一种写法,通过封装一个函数来进行使用,并且这样写更完美的套在爬虫上。

import asyncio
import time
async def func():
    print("你好啊,我叫赛利亚")
    await asyncio.sleep(3) # 异步操作的代码
    print("你好啊,我叫赛利亚")
async def func1():
    print("你好啊,我叫张东墙")
    await asyncio.sleep(2)
    print("你好啊,我叫张东墙")
async def func2():
    print("你好啊,我叫王伟")
    await asyncio.sleep(4)
    print("你好啊,我叫王伟")
async def main():
    # 第一种写法
    # f1 = func()
    # await f1 # 一般await挂起操作放在协程对象前面
    # ....

    # 第二种写法(推荐)
    tasks = [func(), func1(), func2()]
    await asyncio.wait(tasks)
if __name__ == '__main__':
    a = time.time()
    asyncio.run(main())
    b = time.time()
    print(b - a, 's')

【运行结果】

并且执行效率还是和上一个程序一样的。

你好啊,我叫张东墙
你好啊,我叫赛利亚
你好啊,我叫王伟
你好啊,我叫张东墙
你好啊,我叫赛利亚
你好啊,我叫王伟
4.006398916244507 s

但是在输出的时候会有警告,但不影响程序运行。

可以这样改 在使用 asyncio.wait之前,将协程对象包装成tesk对象就可以了,只需要在协程对象之前加一个asyncio.create_task,相当于创建一个协程任务。 

#tasks = [func(), func1(), func2()]
    
    tasks = [
        asyncio.create_task(func()),
        asyncio.create_task(func1()),
        asyncio.create_task(func2())
    ]
    
    await asyncio.wait(tasks)

猜你喜欢

转载自blog.csdn.net/m0_48936146/article/details/124820355