python多任务--协程
2020-10-16 本文已影响0人
小啊小狼
一、前言
协程
协程 ,又称为微线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程。
协程的优势
-
执行效率高,因为子程序切换函数,而不是线程,没有线程切换的开销,由程序自身控制切换。于多线程相比,线程数量越多,切换开销越大,协程的优势越明显
-
不需要锁的机制,只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁。
二、实现协程的几种方式
1、yield(生成器)可以很容易的实现从一个函数切换到另外一个函数
def work1():
for i in range(5):
print(f"work1--befor----{i}")
yield
print(f"work1--after----{i}")
time.sleep(0.5)
def work2():
for i in range(5):
print(f"work2---befor---{i}")
yield
print(f"work2--after----{i}")
time.sleep(0.5)
def main():
g1 = work1()
g2 = work2()
while True:
try:
next(g1)
print('主程序')
next(g2)
except StopIteration:
break
main()
运行结果如下:
image.png
2、原生的协程
import asyncio
# 定义一个协程函数
async def work1():
for i in range(10):
print(f"work1--浇花----{i}")
# 调用协程函数,返回的是一个协程对象
cor1 = work1()
# 执行协程
asyncio.run(cor1)
2.1、使用原生的协程实现多任务(不同任务)
协程中切换,通过await语法来挂起自身的协程。await后面跟上耗时操作,耗时操作一般指IO操作: 网络请求,文件读取等,使用asyncio.sleep模拟耗时操作。协程的目的也是让这些IO操作异步化。
sleep()需要用asyncio.sleep()
await必须要在 async def function(): 中用,否则会报错
import asyncio
async def work1():
for i in range(3):
print(f"work1--浇花----{i}")
await asyncio.sleep(1)
async def work2():
for i in range(5):
print(f"work2--打墙----{i}")
await asyncio.sleep(1)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
# 创建两个协程任务
tasks = [
work1(),
work2(),
]
# 启动事件循环并将协程放进去执行
loop.run_until_complete(asyncio.wait(tasks))
#输出
work1--浇花----0
work2--打墙----0
work1--浇花----1
work2--打墙----1
work1--浇花----2
work2--打墙----2
work2--打墙----3
work2--打墙----4
2.2、使用原生的协程实现多任务(同一方法处理大量数据)
import asyncio
from queue import Queue
import time
def decorator(func):
def wrapper():
# 函数执行之前获取系统时间
start_time = time.time()
func()
# 函数执行之后获取系统时间
end_time = time.time()
print('执行时间为:', end_time - start_time)
return end_time - start_time
return wrapper
async def work1(q):
while q.qsize():
print(f"请求url:{q.get()}")
await asyncio.sleep(0.1)
@decorator
def main():
#创建一个包含有1000条url的队列
q = Queue()
for i in range(1000):
q.put(f"www.baidu.com.{i}")
loop = asyncio.get_event_loop()
# 创建100个协程任务
tasks = [work1(q) for i in range(100)]
# 启动事件循环并将协程放进去执行
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
#输出
...
请求url:www.baidu.com.890
请求url:www.baidu.com.891
请求url:www.baidu.com.892
...
执行时间为: 1.060093641281128
100个协程执行1000个耗时0.1秒的请求只需要1秒
2.3、版本区别:
python 3.7 以前的版本调用异步函数的步骤:(如以上代码)
- 1、调用asyncio.get_event_loop()函数获取事件循环loop对象
- 2、通过不同的策略调用loop.run_forever()方法或者loop.run_until_complete()方法执行异步函数
python3.7 以后的版本
- 1、asyncio.run() 函数用来运行最高层级的入口点,下例的main()函数。此函数总是会创建一个新的事件循环并在结束时关闭之。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次。
- 2、await 等待一个协程,也可以启动一个协程。
- 3、asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。下例并发运行两个work协程
改动后代码如下
#以上省略
async def main():
q = Queue()
for i in range(1000):
q.put(f"www.baidu.com.{i}")
#创建了任务
tasks = [asyncio.create_task(work1(q)) for i in range(100)]
#将任务丢到执行队列里面去
[await t for t in tasks]
if __name__ == '__main__':
m=main()
start_time = time.time()
asyncio.run(m)
end_time = time.time()
print('运行时间{}秒'.format(end_time - start_time))
3、greenlet模块
import time
import greenlet
"""
greenlet:在协程之间只能手动进行切换
"""
def work1():
for i in range(6):
time.sleep(1)
cor2.switch()
print(f'浇花的第{i + 1}秒')
def work2():
for i in range(5):
time.sleep(1)
cor1.switch()
print(f'打墙的第{i + 1}秒')
cor1 = greenlet.greenlet(work1)
cor2 = greenlet.greenlet(work2)
cor1.switch()
4、gevent模块实现多任务
- gevent模块对greenlet又做了一层封装,当程序遇到IO耗时等待的时候会进行自动切换
- gevent中默认是遇到gevent.sleep()会自动进行切换
- 如果让gevent遇到io耗时自动切换:需要在程序的导包处加一个补丁monkey.patch_all(),该补丁不支持多线程
from gevent import monkey
monkey.patch_all()
import gevent
def work1():
for i in range(6):
gevent.sleep(1)
print(f'浇花的第{i + 1}秒')
def work2():
for i in range(5):
gevent.sleep(1)
print(f'打墙的第{i + 1}秒')
# 创建两个协程
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 等待所有协程任务运行完毕
gevent.joinall([g1, g2])
示例:
模拟50000个协程执行对100000个地址的请求
from gevent import monkey
monkey.patch_all()
import gevent
import time
#创建100000个地址
urls = ['http://www.baidu.com' for i in range(100000)]
#定义需要执行的任务函数
def work():
while urls:
url = urls.pop()
# res = requests.get(url)
time.sleep(0.5)
print(f"正在请求url:{url},请求结果:url")
def main():
cos = []
#创建50000个协程
for i in range(50000):
cor = gevent.spawn(work)
cos.append(cor)
# 等待所有协程任务运行完毕
gevent.joinall(cos)
main()