Python并发编程库:Asyncio的异步编程实战

news2024/11/8 23:31:56

Python并发编程库:Asyncio的异步编程实战

在现代应用中,并发和高效的I/O处理是影响系统性能的关键因素之一。Python的asyncio库是专为异步编程设计的模块,提供了一种更加高效、易读的并发编程方式,适用于处理大量的I/O密集型任务(如网络请求、文件操作等)。在这篇博客中,我们将详细介绍如何使用asyncio来进行异步编程,并通过一个实战案例,展示asyncio如何提升程序的性能。
在这里插入图片描述

1. 异步编程基础概念

在开始编码前,我们先理解一些基本概念:

  • 同步:任务按顺序依次执行,只有当前任务执行完成后,下一个任务才会开始执行。
  • 异步:任务可以并发执行,当遇到I/O操作时,程序可以切换到其他任务执行,从而不必等待。
  • 协程(Coroutine):协程是可以被挂起和恢复的函数,用于实现异步执行。在Python中,用async def定义协程函数。
  • 事件循环(Event Loop)asyncio的核心,它负责调度并运行协程,当协程遇到await时就会释放控制权,切换到其他任务。
    在这里插入图片描述

2. Asyncio的核心功能

asyncio库主要由以下几个核心部分组成:

  • 事件循环:管理所有异步任务的调度与执行。
  • 协程函数:用async def定义的函数,可以包含await关键字,表示程序可以在此处暂停并切换任务。
  • 任务(Tasks):将协程封装成任务,让它们在事件循环中并发运行。
  • Future对象:表示一个异步操作的最终结果。

2.1 异步协程函数

asyncio中,用async def定义的函数即为协程函数。协程函数只有在被await调用时才会执行。

import asyncio

async def my_coroutine():
    print("Start coroutine")
    await asyncio.sleep(1)
    print("End coroutine")

# 运行协程
asyncio.run(my_coroutine())

2.2 任务的创建

可以使用asyncio.create_task将协程封装成任务,从而允许多个任务并发执行:

async def task1():
    print("Task 1 start")
    await asyncio.sleep(2)
    print("Task 1 end")

async def task2():
    print("Task 2 start")
    await asyncio.sleep(1)
    print("Task 2 end")

async def main():
    task_1 = asyncio.create_task(task1())
    task_2 = asyncio.create_task(task2())
    
    await task_1
    await task_2

asyncio.run(main())

在上面的代码中,两个任务将并发执行。由于task2的延迟时间较短,因此它会先结束。

2.3 等待多个任务

asyncio.gather可以等待多个协程并发执行并返回结果:

async def fetch_data(n):
    print(f"Fetching data {n}")
    await asyncio.sleep(2)
    return f"Data {n}"

async def main():
    results = await asyncio.gather(fetch_data(1), fetch_data(2), fetch_data(3))
    print(results)

asyncio.run(main())

在这里,asyncio.gather会并发运行三个fetch_data任务,并返回所有任务的结果。
在这里插入图片描述

3. Asyncio异步编程实战

下面我们通过一个网络爬虫的例子展示asyncio的应用。假设我们需要从多个URL中提取数据,如果我们按顺序一个一个地请求这些URL,效率会非常低。我们可以使用asyncio并发请求这些URL,从而显著提升程序性能。

3.1 使用Asyncio实现简单网络爬虫

我们将使用aiohttp库实现异步的HTTP请求。aiohttp是一个支持异步的HTTP客户端,非常适合和asyncio结合使用。

首先,安装aiohttp库:

pip install aiohttp

然后,我们编写异步爬虫代码:

import asyncio
import aiohttp

# 异步获取单个URL数据
async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

# 主函数:使用asyncio.gather并发请求多个URL
async def main(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# 示例URL列表
urls = [
    "http://example.com",
    "http://example.org",
    "http://example.net"
]

# 运行主函数并获取结果
data = asyncio.run(main(urls))
for i, content in enumerate(data):
    print(f"Content of URL {i+1}:")
    print(content[:100])  # 打印前100个字符

在这个代码中,我们并发地请求了多个URL,并获取每个URL的内容。这样做的好处是,程序可以在等待一个URL响应时去处理其他URL请求,极大地提高了效率。

3.2 超时控制与错误处理

在网络请求中,超时和错误处理也是重要的一部分。我们可以为fetch_url添加超时和异常处理,以确保程序在遇到问题时不会崩溃。

async def fetch_url(session, url):
    try:
        async with session.get(url, timeout=5) as response:
            response.raise_for_status()  # 检查响应状态
            return await response.text()
    except asyncio.TimeoutError:
        print(f"Timeout error for URL: {url}")
    except aiohttp.ClientError as e:
        print(f"Error fetching URL {url}: {e}")
    return None  # 返回None表示请求失败

在添加了错误处理后,即使某些URL请求失败,程序也会继续执行。
在这里插入图片描述

4. 性能对比:同步 vs 异步

为了更直观地感受asyncio带来的性能提升,我们可以通过对比同步和异步爬虫的执行时间。

4.1 同步版本爬虫

import requests
import time

def fetch_url_sync(url):
    response = requests.get(url)
    return response.text

# 同步爬虫主函数
def main_sync(urls):
    results = []
    for url in urls:
        results.append(fetch_url_sync(url))
    return results

# 测试同步爬虫
start_time = time.time()
data_sync = main_sync(urls)
end_time = time.time()

print(f"同步爬虫耗时: {end_time - start_time} 秒")

4.2 异步版本爬虫

直接运行我们上面的异步爬虫,并计算其执行时间:

start_time = time.time()
data_async = asyncio.run(main(urls))
end_time = time.time()

print(f"异步爬虫耗时: {end_time - start_time} 秒")

在多个URL请求的场景下,异步爬虫的执行时间通常会比同步爬虫短得多,这展示了asyncio在I/O密集型任务中的显著优势。
在这里插入图片描述

5. 基础总结

上面介绍了asyncio的基本概念及其在Python异步编程中的应用,通过代码实例展示了如何使用asyncio进行异步操作以及如何显著提高程序的并发能力。异步编程虽然学习曲线较高,但在I/O密集型任务中具有明显优势,尤其是在网络请求、文件处理等场景中。
在这里插入图片描述

6. 进阶应用:使用信号量和限制并发数量

在实际应用中,异步任务的数量可能非常多(例如几百或几千个URL请求)。如果全部并发执行,可能会导致系统资源耗尽,甚至触发对方服务器的访问限制。asyncio提供了Semaphore(信号量)机制,可以限制同时执行的任务数量。

下面是如何使用信号量来限制并发任务数的示例:

async def fetch_url_with_semaphore(semaphore, session, url):
    async with semaphore:  # 使用信号量来限制并发数量
        try:
            async with session.get(url, timeout=5) as response:
                return await response.text()
        except Exception as e:
            print(f"Error fetching {url}: {e}")
            return None

async def main_with_semaphore(urls, max_concurrent_tasks=5):
    semaphore = asyncio.Semaphore(max_concurrent_tasks)  # 限制并发数量
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url_with_semaphore(semaphore, session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# 设置最大并发任务数为5
start_time = time.time()
data_with_limit = asyncio.run(main_with_semaphore(urls, max_concurrent_tasks=5))
end_time = time.time()
print(f"使用信号量限制的异步爬虫耗时: {end_time - start_time} 秒")

在这个例子中,我们通过信号量控制了最多只有5个任务同时运行,从而有效管理了系统资源的使用。
在这里插入图片描述

7. 异步上下文管理器

在异步编程中,我们经常需要创建和关闭连接、打开和关闭文件等,这些操作通常需要使用上下文管理器。Python 3.5引入了异步上下文管理器,允许我们用async with来管理异步资源。以aiohttp的Session为例,在异步编程中,这样的上下文管理器能够自动处理连接的关闭,非常方便。

使用异步上下文管理器读取文件

如果需要异步地处理文件操作,可以使用aiofiles库,该库支持异步读取和写入文件。以下是一个读取文件的简单示例:

首先安装aiofiles库:

pip install aiofiles

然后在代码中使用它:

import aiofiles

async def read_file_async(file_path):
    async with aiofiles.open(file_path, mode='r') as file:
        content = await file.read()
        return content

# 示例
async def main():
    content = await read_file_async("example.txt")
    print(content)

asyncio.run(main())

使用异步文件操作在处理大文件或需要高并发的文件操作时非常有用,因为它不会阻塞事件循环。
在这里插入图片描述

8. 小结

asyncio提供了强大的异步编程能力,使得Python在处理I/O密集型任务时的效率得到了显著提升。通过本文介绍的实战示例,你已经掌握了asyncio的核心概念和一些常用技术,包括:

  • 如何定义和运行协程函数
  • 如何并发地执行多个任务
  • 使用asyncio.gather批量并发执行任务
  • 利用信号量来控制并发任务数量
  • 应用异步上下文管理器管理资源

asyncio不仅适用于网络请求和文件操作,也可以应用于多种场景,例如爬虫、聊天应用、数据采集等。掌握asyncio之后,你会发现Python的异步编程能够使程序更加高效、流畅,从而提升系统的整体性能。希望你能在实际项目中将这些技术加以应用,打造更高效的异步系统。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2236176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

当软件质量遇上计划性报废:测试行业该如何应对?

那天,我像往常一样开车在路上,车窗外的风景飞快掠过。就在这时,我在听的一档播客里,突然提到了一个让我不得不停下来思考的词——“计划性报废”。这个词让我愣了一下,伴随着车轮的转动,我的思绪也随之转了…

【Seed-Labs】SQL Injection Attack Lab

Overview SQL 注入是一种代码注入技术,利用的是网络应用程序与数据库服务器之间接口的漏洞。当用户输入的信息在发送到后端数据库服务器之前没有在网络应用程序中进行正确检查时,就会出现这种漏洞。 许多网络应用程序从用户那里获取输入,然…

linux笔记(DNS)

一、概念 DNS(Domain Name System)DNS 是一种分布式网络目录服务,主要用于将人类易于记忆的域名(如 www.example.com)转换为计算机可识别的 IP 地址(如 192.168.1.1)。它就像是互联网的电话簿&a…

【计网】实现reactor反应堆模型 --- 框架搭建

没有一颗星, 会因为追求梦想而受伤, 当你真心渴望某样东西时, 整个宇宙都会来帮忙。 --- 保罗・戈埃罗 《牧羊少年奇幻之旅》--- 实现Reactor反应堆模型 1 前言2 框架搭建3 准备工作4 Reactor类的设计5 Connection连接接口6 回调方法 1 …

minikube 的 Kubernetes 入门教程--(五)

本文记录 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。 这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。 在深入这些步骤之前,先分享来自kubernetes.io教程。 链接>>使用持久卷部署 WordPress 和 MySQL | Kubernetes 获…

算法详解——链表的归并排序非递归解法

算法详解——链表的归并排序非递归解法 本文使用倍增法加上归并排序操作实现了对链表的快速排序,比起一般的递归式归并排序要节省空间并且实现要简单的多,比起一般的迭代式归并排序实现也要简单。 1. 题目假设 给定链表的头结点 head ,请将其…

【网络-交换机】生成树协议、环路检测

路由优先级 路由优先级决定了在多种可达的路由类型中,哪种路由将被用来转发数据包。路由优先级值越低,对应路由的优先级越高,优先级值255表示对应的路由不可达。一般情况下,静态路由的优先级为1,OSPF路由优先级为110&a…

确定图像的熵和各向异性 Halcon entropy_gray 解析

1、图像的熵 1.1 介绍 图像熵(image entropy)是图像“繁忙”程度的估计值,它表示为图像灰度级集合的比特平均数,单位比特/像素,也描述了图像信源的平均信息量。熵指的是体系的混乱程度,对于图像而言&#…

数字后端零基础入门系列 | Innovus零基础LAB学习Day9

Module 16 Wire Editing 这个章节的学习目标是学习如何在innovus中手工画线,切断一根线,换孔,更改一条net shape的layer和width等等。这个技能是每个数字IC后端工程师必须具备的。因为项目后期都需要这些技能来修复DRC和做一些手工custom走线…

除草机器人算法以及技术详解!

算法详解 图像识别与目标检测算法 Yolo算法:这是目标检测领域的一种常用算法,通过卷积神经网络对输入图像进行处理,将图像划分为多个网格,每个网格生成预测框,并通过非极大值抑制(NMS)筛选出最…

ProCalun卡伦纯天然万用膏,全家的皮肤健康守护

受季节交替、生活环境变化、空气污染等方面因素的影响,加上作息不规律导致的免疫力降低,我们或多或少会出现一些如湿疹、痤疮、瘙痒之类的皮肤问题,且反复概率很高。很多人盲目用药,甚至诱发激素依赖性皮炎。所以近年来&#xff0…

Vue 自定义icon组件封装SVG图标

通过自定义子组件CustomIcon.vue使用SVG图标&#xff0c;相比iconfont下载文件、重新替换更节省时间。 子组件包括&#xff1a; 1. Icons.vue 存放所有SVG图标的path 2. CustomIcon.vue 通过icon的id索引对应的图标 使用的时候需要将 <Icons></Icons> 引到使用的…

wireshark工具使用

复制数据 1.右键展开整帧数据 2.复制“所有可见项目” mark标记数据 标记&#xff1a; 跳转&#xff1a; 保存成文件&#xff1a; 文件–>导出特定分组—>Marked packets only

【SpringCloud】SpringBoot集成Swagger 常用Swagger注解

概述&#xff1a;SpringBoot集成Swagger 常用Swagger注解 导语 相信无论是前端还是后端开发&#xff0c;都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力&#xff0c;经常来不及更新。其实无论是前…

Webserver(4.9)本地套接字的通信

目录 本地套接字 本地套接字 TCP\UDP实现不同主机、网络通信 本地套接字实现本地的进程间的通信&#xff0c;类似的&#xff0c;一般采用TCP的通信流程 生成套接字文件 #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h&…

[spring源码]spring配置类解析

解析配置类 在启动Spring时&#xff0c;需要传入一个AppConfig.class给ApplicationContext&#xff0c;ApplicationContext会根据AppConfig类封装为一个BeanDefinition&#xff0c;这种BeanDefinition我们把它称为配置类BeanDefinition AnnotationConfigApplicationContext a…

uni-app跨域set-cookie

set-cookie的值是作为一个权限控制的 首先&#xff0c;无论什么接口都会返回一个set-cookie&#xff0c;但未登录时&#xff0c;set-cookie是没有任何权限的 其次&#xff0c;登录接口请求时会修改set-cookie&#xff0c;并且在后续其他接口发起请求时&#xff0c;会在请求头…

chrdevbase驱动之Makefile优化(指定路径复制、删除文件)

对于学习嵌入式linux驱动篇的chrdevbase虚拟设备驱动时&#xff0c;需要将chrdevbase.c编译成.ko文件&#xff0c;应用层程序里需要把chrdevbaseAPP编译成chrdevbaseAPP可执行文件&#xff0c;此外还需要将生成的*.ko *APP文件拷贝至指定目录下&#xff0c;每次修改或者编译代码…

kafka实时返回浏览数据

在安装完kafka(Docker安装kafka_docker 部署kafka-CSDN博客)&#xff0c;查看容器是否启动&#xff1a; docker ps | grep -E kafka|zookeeper 再用python开启服务 from fastapi import FastAPI, Request from kafka import KafkaProducer import kafka import json import …

使用QtWebEngine的Mac应用如何发布App Store

前言 因为QtWebEngine时第三方包,苹果并不直接支持进行App Store上签名和发布,所以构建和发布一个基于使用QtWebEngine的应用程序并不容易,这里我们对Qt 5.8稍微做一些修改,以便让我们的基于QtWeb引擎的应用程序并让签名能够得到苹果的许可。 QtWebEngine提供了C++和Qml的…