协程是什么,Python中协程有哪些实现方式?

182 阅读2分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

一、 协程

协程不是计算机提供,是程序员任务创造。

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块互相切换执行。例如:

def func1():
    print(1)
    ...
    print(2)
    
def func2():
    print(3)
    ...
    print(4)
    
func1()
func2()

实现协程有这么几种方法:

greenlet,早期模块。

yield关键字。

asyncio装饰器(py3.4)

async、await关键字(py3.5)【推荐】

1、 greenlet实现协程

pip install greenlet
from greenlet import greenlet


def func1():
    print(1)  # 第二步:输出1
    gr2.switch()  # 第三步:切换到func2函数
    print(2)  # 第六步:输出2
    gr2.switch()  # 第七步:切换到func2函数


def func2():
    print(3)  # 第四步:输出3
    gr1.switch()  # 第五步:切换到func1
    print(4)  # 第八步:输出4


gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch()  # 第一步:去执行func1函数

运行结果如下:

image.png

2、 yield关键字

def func1():
    yield 1
    yield from func2()
    yield 2


def func2():
    yield 3
    yield 4


f1 = func1()
for item in f1:
    print(item)

运行结果如下:

3、 asyncio

在Python3.4以及之后的版本。


import asyncio


@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,切换到tasks中的其他任务
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,切换到tasks中的其他任务
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

遇到IO阻塞自动切换。

运行结果如下:

4、 async & await关键字

import asyncio


async def func1():
    print(1)
    await asyncio.sleep(2)  # 遇到IO耗时操作,切换到tasks中的其他任务
    print(2)


async def func2():
    print(3)
    await asyncio.sleep(2)  # 遇到IO耗时操作,切换到tasks中的其他任务
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

运行结果如下:

二、 协程的意义

在一个线程中如果遇到IO等待时间,线程不会傻傻等待,利用空闲的时候再去干点其他事情。

案例:去下载三张图片(网络IO)。

1、 普通方式

import requests


def download_img(url):
    print("开始下载:{}".format(url))
    response = requests.get(url)
    print("下载完成")
    file_name = url.split('/')[-1]
    with open(file_name, 'wb') as f:
        f.write(response.content)


if __name__ == "__main__":
    url_list = [
        'https://img.tupianzj.com/uploads/allimg/202007/9999/82a6ad0de7.jpg',
        'https://img.tupianzj.com/uploads/allimg/180404/9-1P404113S5.jpg',
        'https://img.tupianzj.com/uploads/allimg/202007/9999/0aa6ec4f9b.jpg'
    ]
    for item in url_list:
        download_img(item)

运行结果如下:

2、 协程方式

'''
下载图片使用第三方模块aiohttp,需要提前安装:pip install aiohttp
'''
import asyncio

import aiohttp


async def fetch(session, url):
    print("发送请求:{}".format(url))
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        file_name = url.split('/')[-1]
        with open(file_name, mode='wb') as f:
            f.write(content)
    print("下载完成")


async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://img.tupianzj.com/uploads/allimg/191126/29-191126102621.jpg',
            'https://img.tupianzj.com/uploads/allimg/201910/9999/7801dde033.jpg',
            'https://img.tupianzj.com/uploads/allimg/200624/30-200624103209.jpg'
        ]
        tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
        await asyncio.wait(tasks)


if __name__ == "__main__":
    asyncio.run(main())

运行结果如下: