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 以前的版本调用异步函数的步骤:(如以上代码)

python3.7 以后的版本

改动后代码如下

#以上省略
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模块实现多任务

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()
上一篇下一篇

猜你喜欢

热点阅读