4、协程与asyncio异步框架

本文介绍了协程的概念,它是一种用户态的上下文切换技术,用于在单线程中实现并发。文章详细讨论了同步与异步的区别,并通过greenlet和yield关键字展示了协程的实现方式。此外,还特别提到了asyncio模块在异步爬虫中的应用,以及如何使用uvloop提升事件循环的性能。最后,文章指出在asyncio中与不支持异步的模块混合使用的情况。
部署运行你感兴趣的模型镜像

目录

一、概念

二、使用场景

三、同步与异步的区别

四、协程实现方式

1.greenlet实现协程

2.yield关键字

3.asyncio模块(异步爬虫)

zip函数

4.事件循环

5.uvloop事件循环性能提升

6.await与async关键字

五、asyncio + 不支持异步的模块


一、概念

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。基于单线程来实现并发,即只用一个主线程(可利用的cpu只有一个)情况下实现并发,并发的本质:切换+保存状态。

协程的目的,想要在单线程下实现并发,这里的并发指的是多个任务看起来是同时运行的。

缺点:无法利用CPU多核,一旦协程出现阻塞,将会阻塞整个线程。

二、使用场景

单纯地切换反而会降低代码运行效率,协程适用于网络请求,io阻塞异步场景。

三、同步与异步的区别

同步,是所有的操作都做完,才返回给用户结果。即写完数据库之后,在相应用户,用户体验不好。同步程序的瓶颈在于漫长的I/O等待,想要提高程序效率必须减少I/O等待时间,从提高程序的局部性着手。

异步,不用等所有操作等做完,就相应用户请求。即先相应用户请求,然后慢慢去写数据库,用户体验较好。

四、协程实现方式

1.greenlet实现协程

greenlet、gevent早期模块(了解即可)

遇到IO阻塞时会自动切换任务

from greenlet import greenlet


def func1():
    print(1)        # 第1步:输出 1
    gr2.switch()    # 第3步:切换到 func2 函数
    print(2)        # 第6步:输出 2
    gr2.switch()    # 第7步:切换到 func2 函数,从上一次执行的位置继续向后执行


def func2():
    print(3)        # 第4步:输出 3
    gr1.switch()    # 第5步:切换到 func1 函数,从上一次执行的位置继续向后执行
    print(4)        # 第8步:输出 4


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

gr1.switch() # 第1步:去执行 func1 函数
import gevent
def eat(name):
    print('%s eat 1' %name)
    gevent.sleep(2)
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    gevent.sleep(1)
    print('%s play 2' %name)


g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,name='egon')
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('执行完成')

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模块(异步爬虫)

aiohttp:Client Quickstart — aiohttp 3.8.4 documentationa

aiohttp发送post请求

 async with session.post(url, data=json.dumps(params), headers=headers, verify_ssl=False) as response:
        output = await response.json()
import aiohttp
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

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

async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
            'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
            'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
        ]
        tasks = [ asyncio.create_task(fetch(session, url)) for url in url_list ]

        await asyncio.wait(tasks)


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

之前旧的写法

import aiohttp
import asyncio


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


async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
            'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
            'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
        ]
        tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
        await asyncio.wait(tasks)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    # asyncio.run( result ) # python3.7

zip函数

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。Python zip() 函数 | 菜鸟教程

import asyncio


async def download(url, t):
    print('开始下载', url, t)
    await asyncio.sleep(1)
    print('下载完成', url, t)


async def main():
    urls = [
        'http://www.baidu.com/',
        'http://www.weiwei.com/',
        'http://www.weihubj.shop'
    ]
    ts = [1, 2, 3]
    # task列表
    tasks = [asyncio.create_task(download(url, t)) for url, t in zip(urls, ts)]
    # 等待任务都结束
    await asyncio.wait(tasks)


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

4.事件循环

注意python3.7之后,直接ayncio.run就行,里面封装了事件loop

import asyncio


async def func():
    print("开始执行")


result = func()

loop = asyncio.get_event_loop()
loop.run_until_complete(result)
# asyncio.run( result ) # python3.7

5.uvloop事件循环性能提升

是asyncio的事件循环的替代方案。事件循环 > 默认asyncio的事件循环。性能提升,重点

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

# 编写asyncio的代码,与之前写的代码一致。

# 内部的事件循环自动化会变为uvloop
asyncio.run(...)

6.await与async关键字

await + 可等待的对象(协程对象、Future、Task对象 -> IO等待)

Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象

Task继承Future,Task对象内部await结果的处理基于Future对象来的。(Future对象了解即可)

import asyncio


async def others():
    print("start")
    await asyncio.sleep(2)
    print('end')
    return '返回值'


async def func():
    print("执行协程函数内部代码")

    # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
    response1 = await others()
    print("IO请求结束,结果为:", response1)

    response2 = await others()
    print("IO请求结束,结果为:", response2)

asyncio.run( func() )

函数名前面加上关键字async,表示协程函数;协程函数()不会执行,表示一个协程对象。

func()表示一个协程对象,不会去执行函数

五、asyncio + 不支持异步的模块

效果和协程是一样的,但是线程等待耗费的资源更多,不得已建议才用这种混合的方式

这种混合使用的还不如直接用线程池去做(个人感觉)

requests只能发送同步请求;aiohttp只能发送异步请求;httpx既能发送同步请求

import asyncio
import requests
import json
import uuid
import time

import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())


def func(query, name):
    print(name)
    url = "http://httpbin.org/post"
    data = {
        'name': query
    }
    headers = {
        'content-type': 'application/json',
        'User-Agent': "Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 96.0.4664 .93 Safari / 537.36",
    }
    response = requests.post(url=url, data=json.dumps(data), headers=headers)
    return response.text


async def send_post(query):
    print("开始发送请求", query)

    loop = asyncio.get_event_loop()
    # query,name是传给func函数的参数,
    name = 'weige'
    future_obj = loop.run_in_executor(None, func, query, name)
    response = await future_obj
    print('请求完成')
    print(response)


if __name__ == '__main__':
    start_time = time.time()
    query_list = ['kongtiao', 'xiyiji', 'yifu']
    tasks = [send_post(query) for query in query_list]

    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    end_time = time.time()
    print('执行时间', end_time - start_time)

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

尘缘浮梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值