Python中的asyncio:高效的异步编程模型
随着互联网应用的快速发展,程序的响应性和处理效率成为衡量系统性能的重要指标。传统的同步编程模型在面对高并发和IO密集型任务时,常常显得捉襟见肘,难以满足现代应用的需求。Python的asyncio
库作为一种高效的异步编程模型,为开发者提供了强大的工具来优化程序的性能和响应速度。本文深入探讨了asyncio
的核心概念与机制,详细解析了事件循环、协程、任务和未来对象等关键组件的工作原理。通过大量的代码示例和详尽的中文注释,展示了如何利用asyncio
实现异步任务调度,处理网络请求、文件操作等IO密集型任务,并提升程序的并发处理能力。此外,本文还介绍了asyncio
中的高级功能,如并发控制、超时处理和异常处理,帮助读者构建健壮且高效的异步应用。通过实战案例,读者将掌握使用asyncio
构建高性能网络爬虫的技巧,并了解优化异步程序性能与响应性的最佳实践。本文适合对异步编程感兴趣的Python开发者,以及希望提升程序性能和响应速度的工程师参考学习。
目录
- 引言
asyncio
基础- 2.1 异步编程与同步编程对比
- 2.2
asyncio
的核心概念 - 2.3 事件循环机制
- 协程与任务
- 3.1 协程的定义与使用
- 3.2 创建与管理任务
- 3.3 未来对象(Future Objects)
asyncio
中的IO操作- 4.1 异步网络请求
- 4.2 异步文件操作
- 4.3 异步数据库访问
- 高级功能与优化
- 5.1 并发控制
- 5.2 超时处理
- 5.3 异常处理
- 实战案例:构建高效的网络爬虫
- 6.1 项目需求分析
- 6.2 设计与实现
- 6.3 性能测试与优化
- 优化异步程序的性能与响应性
- 7.1 内存管理
- 7.2 任务调度优化
- 7.3 调试与监控
- 常见问题与解决方案
- 结论
引言
随着互联网应用的普及和数据量的急剧增加,开发者面临着如何高效处理大量并发请求和IO密集型任务的挑战。传统的同步编程模型在处理这些任务时,往往需要通过多线程或多进程来提升性能,但这不仅增加了编程的复杂性,还带来了额外的资源开销。为了解决这一问题,Python引入了asyncio
库,提供了一种基于事件循环的异步编程模型,使得开发者能够在单线程中高效地管理大量并发任务。
asyncio
自Python 3.4版本引入以来,逐渐成为Python生态系统中处理异步任务的核心库。它不仅简化了异步编程的实现,还通过协程(coroutine)和任务(task)的组合,使得代码更加简洁和易读。本文将系统地介绍asyncio
的基本概念、核心机制以及在实际项目中的应用,帮助读者全面掌握这一强大的异步编程工具。
通过本文,读者将了解到如何利用asyncio
实现高效的异步任务调度,处理网络请求、文件操作等常见的IO密集型任务,并提升程序的并发处理能力。此外,本文还将深入探讨asyncio
中的高级功能,如并发控制、超时处理和异常处理,帮助开发者构建更加健壮和高效的异步应用。
asyncio
基础
2.1 异步编程与同步编程对比
在编程中,任务的执行模式主要有同步(synchronous)和异步(asynchronous)两种。了解这两者的区别对于选择合适的编程模型至关重要。
同步编程指的是任务按顺序执行,一个任务完成后才能执行下一个任务。在这种模式下,如果某个任务需要等待(例如IO操作),整个程序将会被阻塞,直到该任务完成。这种阻塞行为可能导致程序响应缓慢,尤其是在处理大量并发请求时。
import timedef fetch_data():print("开始获取数据...")time.sleep(2) # 模拟IO操作print("数据获取完成")return "数据"def main():data = fetch_data()print(f"获取到的数据: {data}")if __name__ == "__main__":main()
上述代码中,fetch_data
函数模拟了一个需要等待2秒的IO操作。在执行过程中,程序在time.sleep(2)
处被阻塞,直到数据获取完成。
异步编程则允许程序在等待某个任务完成时,继续执行其他任务,从而提高程序的并发性和响应速度。通过事件循环(event loop)和协程(coroutine)的协作,异步编程能够在单线程中高效地管理大量并发任务,避免了多线程带来的复杂性和资源开销。
2.2 asyncio
的核心概念
asyncio
库是Python中用于编写异步代码的标准库,其核心概念包括:
- 事件循环(Event Loop):管理和调度异步任务的核心机制,负责监听和分发事件。
- 协程(Coroutine):一种特殊的函数,支持异步执行,使用
async
和await
关键字定义。 - 任务(Task):协程的包装器,负责调度和执行协程。
- 未来对象(Future):表示一个尚未完成的异步操作,协程可以等待未来对象的结果。
2.3 事件循环机制
事件循环是asyncio
的核心,负责调度和执行所有的异步任务。它不断地检查是否有任务准备就绪,并执行相应的协程。
以下是一个简单的事件循环示例:
import asyncioasync def hello():print("Hello")await asyncio.sleep(1)print("World")def main():loop = asyncio.get_event_loop()loop.run_until_complete(hello())loop.close()if __name__ == "__main__":main()
代码解释:
- 定义协程:
hello
是一个协程函数,使用async
关键字定义。在协程内部,通过await
关键字等待asyncio.sleep(1)
,模拟一个异步IO操作。 - 获取事件循环:
loop = asyncio.get_event_loop()
获取当前的事件循环。 - 运行协程:
loop.run_until_complete(hello())
将协程任务提交给事件循环并运行,直到任务完成。 - 关闭事件循环:
loop.close()
关闭事件循环,释放资源。
输出结果:
Hello
World
在这个示例中,事件循环首先执行hello
协程,打印“Hello”,然后等待1秒,最后打印“World”。由于await asyncio.sleep(1)
是一个非阻塞的等待,事件循环可以在等待期间执行其他任务(如果有)。
协程与任务
3.1 协程的定义与使用
协程是异步编程的基石,允许函数在执行过程中暂停和恢复,从而实现并发操作。在asyncio
中,协程使用async def
语法定义,并通过await
关键字调用其他协程或异步函数。
定义协程:
import asyncioasync def fetch_data():print("开始获取数据...")await asyncio.sleep(2) # 模拟IO操作print("数据获取完成")return "数据"
调用协程:
要调用协程,可以通过事件循环来执行:
def main():loop = asyncio.get_event_loop()data = loop.run_until_complete(fetch_data())print(f"获取到的数据: {data}")loop.close()if __name__ == "__main__":main()
输出结果:
开始获取数据...
数据获取完成
获取到的数据: 数据
使用asyncio.run
简化事件循环管理:
自Python 3.7起,可以使用asyncio.run
简化事件循环的创建和关闭:
import asyncioasync def fetch_data():print("开始获取数据...")await asyncio.sleep(2)print("数据获取完成")return "数据"async def main():data = await fetch_data()print(f"获取到的数据: {data}")if __name__ == "__main__":asyncio.run(main())
输出结果与之前相同。
3.2 创建与管理任务
在实际应用中,通常需要同时执行多个协程任务。asyncio
提供了asyncio.create_task
和asyncio.gather
等方法,方便地创建和管理并发任务。
使用asyncio.create_task
创建任务:
import asyncioasync def task1():print("任务1开始")await asyncio.sleep(2)print("任务1完成")return "结果1"async def task2():print("任务2开始")await asyncio.sleep(1)print("任务2完成")return "结果2"async def main():# 创建任务t1 = asyncio.create_task(task1())t2 = asyncio.create_task(task2())# 等待任务完成并获取结果result1 = await t1result2 = await t2print(f"任务1结果: {result1}")print(f"任务2结果: {result2}")if __name__ == "__main__":asyncio.run(main())
输出结果:
任务1开始
任务2开始
任务2完成
任务1完成
任务1结果: 结果1
任务2结果: 结果2
解释:
- 创建任务:使用
asyncio.create_task
将协程包装为任务,并立即开始执行。 - 并发执行:任务1和任务2几乎同时开始执行,任务2由于等待时间较短,先完成。
- 获取结果:通过
await
关键字等待任务完成,并获取返回结果。
使用asyncio.gather
并发执行多个任务:
import asyncioasync def task1():print("任务1开始")await asyncio.sleep(2)print("任务1完成")return "结果1"async def task2():print("任务2开始")await asyncio.sleep(1)print("任务2完成")return "结果2"async def main():# 并发执行任务results = await asyncio.gather(task1(), task2())print(f"所有任务结果: {results}")if __name__ == "__main__":asyncio.run(main())
输出结果:
任务1开始
任务2开始
任务2完成
任务1完成
所有任务结果: ['结果1', '结果2']
解释:
asyncio.gather
将多个协程任务打包,并并发执行,等待所有任务完成后返回结果列表。
3.3 未来对象(Future Objects)
未来对象(Future
)表示一个尚未完成的异步操作,可以通过它来获取异步任务的结果。Future
对象通常由事件循环创建和管理。
创建和使用Future
对象:
import asyncioasync def set_future(fut):print("设置Future的结果...")await asyncio.sleep(2)fut.set_result("Future的结果")async def main():# 创建Future对象fut = asyncio.Future()# 启动协程设置Future的结果asyncio.create_task(set_future(fut))print("等待Future的结果...")result = await futprint(f"获取到的Future结果: {result}")if __name__ == "__main__":asyncio.run(main())
输出结果:
等待Future的结果...
设置Future的结果...
获取到的Future结果: Future的结果
解释:
- 创建Future:通过
asyncio.Future()
创建一个Future对象。 - 设置结果:通过
set_result
方法在协程中设置Future的结果。 - 等待结果:在主协程中通过
await fut
等待Future完成,并获取结果。
Future对象在复杂的异步任务管理中非常有用,例如在回调函数中传递结果,或者在事件驱动的系统中协调多个任务。
asyncio
中的IO操作
asyncio
在处理IO密集型任务时表现尤为出色,如网络请求、文件操作和数据库访问等。以下将介绍如何使用asyncio
进行异步网络请求、文件操作和数据库访问。
4.1 异步网络请求
在网络编程中,常见的IO操作包括HTTP请求、TCP连接等。使用asyncio
可以高效地管理多个并发网络请求。
使用aiohttp
进行异步HTTP请求:
aiohttp
是一个基于asyncio
的异步HTTP客户端/服务器框架,适用于执行大量并发HTTP请求。
安装aiohttp
:
pip install aiohttp
示例代码:
import asyncio
import aiohttpasync def fetch(session, url):async with session.get(url) as response:status = response.statusdata = await response.text()print(f"URL: {url} | 状态码: {status}")return dataasync def main():urls = ["https://www.python.org","https://www.asyncio.org","https://www.github.com","https://www.stackoverflow.com"]async with aiohttp.ClientSession() as session:tasks = [fetch(session, url) for url in urls]results = await asyncio.gather(*tasks)print("所有请求完成")if __name__ == "__main__":asyncio.run(main())
输出示例:
URL: https://www.python.org | 状态码: 200
URL: https://www.asyncio.org | 状态码: 404
URL: https://www.github.com | 状态码: 200
URL: https://www.stackoverflow.com | 状态码: 200
所有请求完成
代码解释:
- 定义
fetch
协程:使用aiohttp
的ClientSession
发送GET请求,并异步获取响应内容。 - 创建任务列表:为每个URL创建一个
fetch
任务。 - 并发执行任务:使用
asyncio.gather
并发执行所有任务,等待所有任务完成。 - 打印结果:打印每个URL的状态码,最后打印“所有请求完成”。
处理大量并发请求:
当需要处理成百上千的并发请求时,合理控制并发数量可以避免过度占用系统资源。可以使用asyncio.Semaphore
进行并发控制。
示例代码:
import asyncio
import aiohttpasync def fetch(session, url, semaphore):async with semaphore:async with session.get(url) as response:status = response.statusdata = await response.text()print(f"URL: {url} | 状态码: {status}")return dataasync def main():urls = [f"https://www.example.com/page{i}" for i in range(1, 101)] # 假设100个URLsemaphore = asyncio.Semaphore(10) # 最大并发数为10async with aiohttp.ClientSession() as session:tasks = [fetch(session, url, semaphore) for url in urls]results = await asyncio.gather(*tasks)print("所有请求完成")if __name__ == "__main__":asyncio.run(main())
代码解释:
- 创建信号量:
asyncio.Semaphore(10)
限制同时执行的任务数为10。 - 在
fetch
协程中使用信号量:通过async with semaphore
确保并发任务数不超过10。 - 生成100个URL任务:模拟大量并发请求。
- 执行并发任务:使用
asyncio.gather
执行所有任务,等待完成。
4.2 异步文件操作
在文件IO操作中,尤其是处理大量文件时,同步操作会导致程序阻塞。asyncio
可以结合aiofiles
库,实现异步文件操作。
安装aiofiles
:
pip install aiofiles
示例代码:
import asyncio
import aiofilesasync def read_file(file_path):async with aiofiles.open(file_path, mode='r') as f:contents = await f.read()print(f"读取文件 {file_path} 完成")return contentsasync def write_file(file_path, data):async with aiofiles.open(file_path, mode='w') as f:await f.write(data)print(f"写入文件 {file_path} 完成")async def main():read_tasks = [read_file(f"input_{i}.txt") for i in range(1, 6)]contents = await asyncio.gather(*read_tasks)write_tasks = [write_file(f"output_{i}.txt", content.upper()) for i, content in enumerate(contents, 1)]await asyncio.gather(*write_tasks)if __name__ == "__main__":asyncio.run(main())
代码解释:
- 定义
read_file
协程:异步读取文件内容。 - 定义
write_file
协程:异步写入文件内容。 - 创建读取任务:异步读取多个输入文件。
- 处理数据并创建写入任务:将读取的内容转换为大写,并异步写入多个输出文件。
- 执行并发任务:使用
asyncio.gather
并发执行所有读取和写入任务。
注意事项:
aiofiles
不支持所有文件操作,例如随机访问等复杂操作。- 异步文件操作适用于处理大量文件的读取和写入任务,能够显著提高效率。
4.3 异步数据库访问
在数据库操作中,尤其是需要处理大量并发查询时,异步访问能够提高数据库的吞吐量和响应速度。可以使用asyncpg
库进行异步PostgreSQL数据库操作。
安装asyncpg
:
pip install asyncpg
示例代码:
import asyncio
import asyncpgasync def fetch_user(pool, user_id):async with pool.acquire() as connection:row = await connection.fetchrow("SELECT * FROM users WHERE id = $1", user_id)print(f"用户ID: {user_id} | 用户名: {row['name']}")return rowasync def main():# 创建数据库连接池pool = await asyncpg.create_pool(user='youruser', password='yourpassword',database='yourdb', host='127.0.0.1', port=5432)user_ids = range(1, 101) # 假设查询100个用户tasks = [fetch_user(pool, user_id) for user_id in user_ids]results = await asyncio.gather(*tasks)await pool.close()if __name__ == "__main__":asyncio.run(main())
代码解释:
- 创建数据库连接池:通过
asyncpg.create_pool
创建一个连接池,管理数据库连接。 - 定义
fetch_user
协程:异步查询指定用户ID的用户信息。 - 创建并发查询任务:为100个用户ID创建查询任务。
- 执行并发任务:使用
asyncio.gather
并发执行所有查询任务。 - 关闭连接池:任务完成后关闭连接池,释放资源。
优势:
- 高并发处理:通过连接池和异步查询,能够高效地处理大量并发数据库请求。
- 资源优化:连接池管理数据库连接,避免频繁创建和关闭连接,优化资源利用。
高级功能与优化
在实际应用中,除了基本的异步任务调度,asyncio
还提供了多种高级功能,帮助开发者构建更加高效和健壮的异步应用。
5.1 并发控制
在处理大量并发任务时,合理控制并发数量可以避免系统资源过载,提高程序的稳定性和性能。asyncio.Semaphore
提供了一种简单的并发控制机制。
使用asyncio.Semaphore
限制并发任务数:
import asyncio
import aiohttpasync def fetch(session, url, semaphore):async with semaphore:async with session.get(url) as response:status = response.statusdata = await response.text()print(f"URL: {url} | 状态码: {status}")return dataasync def main():urls = [f"https://www.example.com/page{i}" for i in range(1, 21)] # 20个URLsemaphore = asyncio.Semaphore(5) # 最大并发数为5async with aiohttp.ClientSession() as session:tasks = [fetch(session, url, semaphore) for url in urls]results = await asyncio.gather(*tasks)print("所有请求完成")if __name__ == "__main__":asyncio.run(main())
代码解释:
- 创建信号量:
asyncio.Semaphore(5)
限制同时执行的任务数为5。 - 在
fetch
协程中使用信号量:通过async with semaphore
确保并发任务数不超过5。 - 生成并发任务:创建20个URL请求任务,实际同时执行的任务数不会超过5。
应用场景:
- 网络爬虫:限制同时进行的HTTP请求数,避免被目标服务器封禁。
- 数据库查询:控制并发数据库连接数,避免过载数据库服务器。
5.2 超时处理
在异步编程中,某些任务可能由于网络问题或其他原因长时间未完成。合理设置超时可以防止程序无限等待,提高系统的健壮性。
使用asyncio.wait_for
设置超时:
import asyncio
import aiohttpasync def fetch(session, url):try:async with session.get(url) as response:data = await asyncio.wait_for(response.text(), timeout=3.0) # 设置3秒超时print(f"成功获取URL: {url}")return dataexcept asyncio.TimeoutError:print(f"请求超时: {url}")return Noneasync def main():urls = ["https://www.python.org","https://www.asyncio.org","https://www.github.com","https://www.nonexistenturl.org" # 假设此URL不可访问]async with aiohttp.ClientSession() as session:tasks = [fetch(session, url) for url in urls]results = await asyncio.gather(*tasks)print("所有请求完成")if __name__ == "__main__":asyncio.run(main())
输出示例:
成功获取URL: https://www.python.org
成功获取URL: https://www.asyncio.org
请求超时: https://www.github.com
请求超时: https://www.nonexistenturl.org
所有请求完成
代码解释:
- 设置超时:使用
asyncio.wait_for
为response.text()
设置3秒的超时时间。 - 处理超时异常:捕获
asyncio.TimeoutError
异常,处理请求超时的情况。 - 执行任务:并发执行所有请求任务,等待完成。
注意事项:
- 合理设置超时:根据实际网络环境和任务需求,合理设置超时时间,避免过短导致频繁超时或过长导致资源浪费。
- 异常处理:在异步任务中,务必处理可能的异常,防止程序崩溃。
5.3 异常处理
在异步编程中,任务可能会因各种原因失败,例如网络错误、文件不存在等。合理的异常处理机制能够提高程序的健壮性和可靠性。
使用try-except
捕获协程中的异常:
import asyncio
import aiohttpasync def fetch(session, url):try:async with session.get(url) as response:if response.status != 200:raise aiohttp.ClientError(f"HTTP错误: {response.status}")data = await response.text()print(f"成功获取URL: {url}")return dataexcept aiohttp.ClientError as e:print(f"请求失败: {url} | 错误: {e}")return Noneasync def main():urls = ["https://www.python.org","https://www.asyncio.org","https://www.github.com","https://www.nonexistenturl.org" # 假设此URL不可访问]async with aiohttp.ClientSession() as session:tasks = [fetch(session, url) for url in urls]results = await asyncio.gather(*tasks, return_exceptions=True)print("所有请求完成")if __name__ == "__main__":asyncio.run(main())
输出示例:
成功获取URL: https://www.python.org
成功获取URL: https://www.asyncio.org
成功获取URL: https://www.github.com
请求失败: https://www.nonexistenturl.org | 错误: HTTP错误: 404
所有请求完成
代码解释:
- 捕获HTTP错误:在
fetch
协程中,如果响应状态码不是200,抛出aiohttp.ClientError
异常。 - 处理异常:通过
try-except
块捕获并处理异常,防止程序崩溃。 - 使用
return_exceptions=True
:在asyncio.gather
中设置return_exceptions=True
,允许任务返回异常对象,而不是在遇到异常时立即中断。
注意事项:
- 具体异常类型:尽量捕获具体的异常类型,避免过于宽泛的异常捕获。
- 日志记录:在异常处理过程中,可以记录详细的日志,便于后续调试和问题排查。
实战案例:构建高效的网络爬虫
为了更好地理解asyncio
的应用,本文将通过一个实战案例,展示如何使用asyncio
构建一个高效的网络爬虫,能够同时处理大量并发HTTP请求,并高效地抓取网页内容。
6.1 项目需求分析
假设我们需要抓取多个网站的首页内容,并统计每个页面中的关键词出现次数。由于需要处理大量网站,使用传统的同步爬虫效率较低,无法满足需求。因此,我们将使用asyncio
和aiohttp
构建一个高效的异步爬虫。
项目功能需求:
- 从给定的URL列表中抓取网页内容。
- 解析网页内容,统计特定关键词的出现次数。
- 并发处理多个请求,提高爬取效率。
- 处理请求超时和异常情况,确保爬虫的稳定性。
- 输出每个URL的关键词统计结果。
6.2 设计与实现
项目结构:
async_crawler/
├── crawler.py
├── urls.txt
└── keywords.txt
crawler.py
:主程序,负责异步爬取和关键词统计。urls.txt
:包含待抓取的URL列表。keywords.txt
:包含需要统计的关键词列表。
步骤概述:
- 读取URL和关键词列表。
- 定义异步爬虫协程。
- 使用
asyncio.Semaphore
控制并发数。 - 抓取网页内容并统计关键词。
- 输出统计结果。
示例代码:
import asyncio
import aiohttp
import aiofiles
import re
from collections import defaultdictasync def read_file(file_path):"""异步读取文件内容"""async with aiofiles.open(file_path, mode='r') as f:contents = await f.read()return contents.splitlines()async def fetch(session, url, semaphore):"""异步抓取网页内容"""try:async with semaphore:async with session.get(url, timeout=10) as response:if response.status != 200:print(f"请求失败: {url} | 状态码: {response.status}")return url, Nonetext = await response.text()print(f"成功获取URL: {url}")return url, textexcept asyncio.TimeoutError:print(f"请求超时: {url}")return url, Noneexcept aiohttp.ClientError as e:print(f"请求错误: {url} | 错误: {e}")return url, Nonedef count_keywords(text, keywords):"""统计关键词出现次数"""counts = defaultdict(int)for keyword in keywords:counts[keyword] = len(re.findall(rf'\b{re.escape(keyword)}\b', text, re.IGNORECASE))return countsasync def process_url(session, url, semaphore, keywords):"""处理单个URL的抓取和关键词统计"""url, text = await fetch(session, url, semaphore)if text:counts = count_keywords(text, keywords)return url, countselse:return url, Noneasync def main():# 读取URL和关键词列表urls = await read_file('urls.txt')keywords = await read_file('keywords.txt')# 设置并发数semaphore = asyncio.Semaphore(10)async with aiohttp.ClientSession() as session:tasks = [process_url(session, url, semaphore, keywords) for url in urls]results = await asyncio.gather(*tasks)# 输出结果async with aiofiles.open('results.txt', mode='w') as f:for url, counts in results:if counts:await f.write(f"URL: {url}\n")for keyword, count in counts.items():await f.write(f" {keyword}: {count}\n")await f.write("\n")else:await f.write(f"URL: {url} | 无法获取内容\n\n")print("所有URL处理完成,结果已保存到 results.txt")if __name__ == "__main__":asyncio.run(main())
代码详解:
-
读取文件内容:
read_file
协程异步读取文件内容,并返回按行分割的列表。- 分别读取
urls.txt
和keywords.txt
,获取待抓取的URL和需要统计的关键词。
-
异步抓取网页内容:
fetch
协程使用aiohttp
发送GET请求,获取网页内容。- 使用
asyncio.Semaphore
限制并发请求数,避免过度请求导致被封禁。 - 处理请求超时和HTTP错误,确保程序的稳定性。
-
关键词统计:
count_keywords
函数使用正则表达式统计每个关键词在网页内容中出现的次数。- 使用
defaultdict
简化计数过程。
-
处理单个URL:
process_url
协程结合抓取和关键词统计,返回每个URL的统计结果。
-
执行并发任务:
- 在
main
协程中,创建并发任务列表,并使用asyncio.gather
并发执行所有任务。 - 抓取完成后,异步写入结果到
results.txt
文件。
- 在
-
运行程序:
- 使用
asyncio.run(main())
启动异步事件循环,执行爬虫任务。
- 使用
示例输入文件:
urls.txt
:
https://www.python.org
https://www.asyncio.org
https://www.github.com
https://www.stackoverflow.com
keywords.txt
:
Python
asyncio
GitHub
StackOverflow
示例输出文件:
results.txt
:
URL: https://www.python.orgPython: 10asyncio: 2GitHub: 0StackOverflow: 0URL: https://www.asyncio.orgPython: 5asyncio: 8GitHub: 0StackOverflow: 0URL: https://www.github.comPython: 3asyncio: 1GitHub: 15StackOverflow: 0URL: https://www.stackoverflow.comPython: 4asyncio: 1GitHub: 0StackOverflow: 20
性能优势:
- 高并发处理:通过
asyncio
和aiohttp
,爬虫能够同时处理多个HTTP请求,显著提高爬取效率。 - 资源优化:使用
asyncio.Semaphore
限制并发数,避免系统资源过载。 - 稳定性:合理的异常处理机制,确保部分请求失败不会影响整体程序运行。
6.3 性能测试与优化
在构建高效的异步爬虫后,进行性能测试和优化是确保程序达到最佳性能的关键步骤。
性能测试:
通过对不同并发数和任务数量的测试,评估爬虫的性能表现。
示例代码:
import asyncio
import aiohttp
import timeasync def fetch(session, url, semaphore):async with semaphore:async with session.get(url) as response:await response.text()return response.statusasync def main(concurrent, total):urls = [f"https://www.example.com/page{i}" for i in range(total)]semaphore = asyncio.Semaphore(concurrent)async with aiohttp.ClientSession() as session:tasks = [fetch(session, url, semaphore) for url in urls]start = time.time()results = await asyncio.gather(*tasks)end = time.time()print(f"并发数: {concurrent} | 总任务数: {total} | 耗时: {end - start:.2f}秒")if __name__ == "__main__":# 测试不同并发数和任务数asyncio.run(main(concurrent=10, total=100))asyncio.run(main(concurrent=50, total=100))asyncio.run(main(concurrent=100, total=100))
代码解释:
- 定义
fetch
协程:异步发送GET请求,获取响应状态码。 - 定义
main
协程:根据指定的并发数和任务总数,生成任务并执行。 - 记录耗时:通过
time.time()
记录任务执行的开始和结束时间,计算总耗时。 - 运行测试:分别测试并发数为10、50和100时的性能表现。
示例输出:
并发数: 10 | 总任务数: 100 | 耗时: 20.35秒
并发数: 50 | 总任务数: 100 | 耗时: 8.72秒
并发数: 100 | 总任务数: 100 | 耗时: 5.43秒
优化建议:
- 合理设置并发数:根据系统资源和目标服务器的承载能力,合理设置并发数,避免过高导致资源耗尽或被目标服务器封禁。
- 优化任务分配:通过合理分配任务,平衡各协程的工作负载,提高整体效率。
- 缓存与复用:对于重复请求的URL,可以考虑使用缓存机制,减少不必要的网络请求。
优化异步程序的性能与响应性
为了确保异步程序在高并发和大规模任务下仍能保持高效和稳定,需要采取多种优化策略,包括内存管理、任务调度优化以及调试与监控。
7.1 内存管理
在异步编程中,内存管理尤为重要,尤其是在处理大量数据或长时间运行的程序时。以下是一些内存管理的最佳实践:
-
使用生成器:避免一次性加载大量数据,使用生成器逐步生成数据,减少内存占用。
async def generate_urls(total):for i in range(total):yield f"https://www.example.com/page{i}"async def main():async for url in generate_urls(1000):print(url)asyncio.run(main())
-
及时释放资源:在使用完资源(如文件、网络连接)后,及时关闭或释放,避免内存泄漏。
async with aiofiles.open('file.txt', mode='r') as f:contents = await f.read() # 文件已自动关闭
-
限制并发数:通过信号量或队列限制同时执行的任务数,避免因过多任务导致内存占用过高。
semaphore = asyncio.Semaphore(10)
7.2 任务调度优化
合理的任务调度能够提升异步程序的执行效率,减少等待时间。以下是一些任务调度优化的方法:
-
任务优先级:根据任务的重要性和紧急程度,设置不同的优先级,优先执行高优先级任务。
import asyncio import heapqclass PriorityTask:def __init__(self, priority, coro):self.priority = priorityself.coro = corodef __lt__(self, other):return self.priority < other.priorityasync def worker(queue):while True:priority_task = await queue.get()if priority_task is None:breakawait priority_task.coroqueue.task_done()async def main():queue = asyncio.Queue()workers = [asyncio.create_task(worker(queue)) for _ in range(3)]# 添加高优先级任务await queue.put(PriorityTask(1, fetch(session, url1, semaphore)))# 添加低优先级任务await queue.put(PriorityTask(10, fetch(session, url2, semaphore)))# 等待所有任务完成await queue.join()# 停止工作者for _ in workers:await queue.put(None)await asyncio.gather(*workers)
-
任务分组:将相关任务分组处理,减少任务切换的开销。
async def process_group(group):tasks = [fetch(session, url, semaphore) for url in group]results = await asyncio.gather(*tasks)return results
-
合理安排任务顺序:根据任务的依赖关系和执行时间,安排合适的任务顺序,优化整体执行时间。
7.3 调试与监控
在开发和维护异步程序时,调试和监控是确保程序稳定运行的重要环节。以下是一些调试与监控的技巧:
-
使用日志:在关键位置添加日志,记录程序的运行状态和异常信息,便于问题排查。
import logginglogging.basicConfig(level=logging.INFO)async def fetch(session, url, semaphore):try:async with semaphore:async with session.get(url) as response:data = await response.text()logging.info(f"成功获取URL: {url}")return dataexcept Exception as e:logging.error(f"请求失败: {url} | 错误: {e}")return None
-
利用调试工具:使用
asyncio
支持的调试工具,如pdb
,结合断点调试,逐步排查问题。import asyncio import pdbasync def fetch(session, url, semaphore):pdb.set_trace()async with semaphore:async with session.get(url) as response:data = await response.text()return data
-
监控事件循环:通过事件循环的监控工具,如
asyncio
的debug
模式,检测潜在的性能瓶颈和资源泄漏。import asyncioasync def main():loop = asyncio.get_running_loop()loop.set_debug(True)# 其他异步任务
-
使用第三方监控工具:集成第三方监控工具,如
aiohttp
的中间件,监控请求的响应时间和错误率。
常见问题与解决方案
在使用asyncio
进行异步编程时,开发者可能会遇到各种问题。以下是一些常见问题及其解决方案:
问题1:协程未被正确执行
症状: 定义的协程没有被执行,程序直接结束。
原因: 协程没有被提交给事件循环执行。
解决方案: 确保协程通过asyncio.run
、loop.run_until_complete
或asyncio.create_task
等方式被正确执行。
示例代码:
import asyncioasync def say_hello():print("Hello, asyncio!")def main():say_hello() # 错误:协程未被执行asyncio.run(say_hello()) # 正确if __name__ == "__main__":main()
问题2:事件循环被多次关闭
症状: 报错信息提示“Event loop is closed”。
原因: 尝试在已关闭的事件循环上执行协程。
解决方案: 避免在事件循环关闭后再次使用它,或者重新创建一个新的事件循环。
示例代码:
import asyncioasync def main():print("Hello")def run_twice():asyncio.run(main())asyncio.run(main()) # 错误:事件循环已关闭if __name__ == "__main__":run_twice()
解决方法:
将两次运行放在不同的事件循环中,或避免重复关闭事件循环。
问题3:协程未被等待
症状: 协程未执行或部分任务未完成。
原因: 协程被定义但未被await
或未提交为任务。
解决方案: 确保所有协程被await
或通过asyncio.create_task
提交给事件循环。
示例代码:
import asyncioasync def greet():print("Hello, World!")async def main():greet() # 错误:协程未被等待await greet() # 正确if __name__ == "__main__":asyncio.run(main())
问题4:阻塞操作阻塞事件循环
症状: 异步任务卡住,无法并发执行。
原因: 在异步程序中执行了阻塞操作(如time.sleep
、CPU密集型计算等),阻塞了事件循环。
解决方案: 避免在异步程序中执行阻塞操作,或将阻塞操作放到线程池或进程池中执行。
示例代码:
import asyncio
import timeasync def blocking_task():time.sleep(2) # 错误:阻塞事件循环print("阻塞任务完成")async def main():await blocking_task()if __name__ == "__main__":asyncio.run(main())
正确做法:
使用asyncio.sleep
替代time.sleep
,或将阻塞任务放到线程池中执行。
import asyncio
import timeasync def blocking_task():loop = asyncio.get_running_loop()await loop.run_in_executor(None, time.sleep, 2) # 在默认线程池中执行阻塞操作print("阻塞任务完成")async def main():await blocking_task()if __name__ == "__main__":asyncio.run(main())
问题5:无法捕获异步任务中的异常
症状: 异步任务抛出的异常未被捕获,导致程序崩溃或行为异常。
原因: 异步任务中的异常未被正确处理。
解决方案: 在协程内部使用try-except
块捕获异常,或在asyncio.gather
中设置return_exceptions=True
以便捕获所有异常。
示例代码:
import asyncioasync def faulty_task():raise ValueError("发生错误")async def main():tasks = [faulty_task()]results = await asyncio.gather(*tasks) # 默认情况下,异常会被抛出print(results)if __name__ == "__main__":asyncio.run(main())
输出:
Traceback (most recent call last):...
ValueError: 发生错误
解决方法:
使用try-except
捕获异常,或设置return_exceptions=True
。
import asyncioasync def faulty_task():raise ValueError("发生错误")async def main():tasks = [faulty_task()]results = await asyncio.gather(*tasks, return_exceptions=True)for result in results:if isinstance(result, Exception):print(f"捕获到异常: {result}")else:print(f"任务结果: {result}")if __name__ == "__main__":asyncio.run(main())
输出:
捕获到异常: 发生错误
结论
asyncio
作为Python中用于编写高效异步代码的标准库,通过协程和事件循环的组合,为开发者提供了一种简洁而强大的异步编程模型。本文系统地介绍了asyncio
的核心概念、协程与任务的管理方法,以及在处理IO密集型任务中的应用。通过详尽的代码示例和中文注释,展示了如何利用asyncio
实现高效的异步任务调度,处理网络请求、文件操作和数据库访问等常见任务。
在实际项目中,asyncio
的高级功能,如并发控制、超时处理和异常处理,进一步增强了异步程序的性能和稳定性。通过实战案例,读者能够掌握使用asyncio
构建高效网络爬虫的技巧,并了解优化异步程序性能与响应性的最佳实践。
然而,异步编程也带来了一些挑战,如复杂的调试过程和对异步概念的深入理解要求。开发者需要熟练掌握asyncio
的工作原理和最佳实践,才能充分发挥其优势,构建出高性能、响应迅速的应用程序。
随着异步编程在各类应用场景中的广泛应用,掌握asyncio
将成为Python开发者提升程序性能和处理大规模并发任务的重要技能。通过不断学习和实践,开发者能够更好地应对现代软件开发中的高并发和高效能需求,推动技术创新和业务发展。
相关文章:
Python中的asyncio:高效的异步编程模型
随着互联网应用的快速发展,程序的响应性和处理效率成为衡量系统性能的重要指标。传统的同步编程模型在面对高并发和IO密集型任务时,常常显得捉襟见肘,难以满足现代应用的需求。Python的asyncio库作为一种高效的异步编程模型,为开发…...
《解锁鸿蒙系统AI能力,开启智能应用开发新时代》
在当今科技飞速发展的时代,鸿蒙系统以其独特的分布式架构和强大的AI能力,为开发者们带来了前所未有的机遇。本文将深入探讨开发者如何利用鸿蒙系统的AI能力开发更智能的应用,开启智能应用开发的新时代。 鸿蒙系统构筑了15系统级的AI能力&…...
安卓OCR使用(Google ML Kit)
OCR是一个很常用的功能,Google ML Kit提供了OCR能力,用起来也很简单,本文介绍一下使用方法。 1. 相关概念 名词概念解释TextBlock块一个段落Line行一行文本Element元素单词;对汉字来说,类似"开头 (分隔符)中间&…...
使用redis的5种常用场景
文章目录 1. 缓存热点数据2. 分布式锁3. 计数器和限流器4. 消息队列5. 会话管理总结 在日常开发工作中,Redis作为一款高性能的内存数据库,凭借其强大的功能特性和卓越的性能表现,已经成为了许多项目中不可或缺的组件。本文将详细介绍Redis在实…...
Extreme670和440的DHCP和vlan划分
1.网关配置 防火墙 USG 添加静态路由,也就是回指路由192.168.0.0 255.255.0.0 192.168.100.2 usg关闭DHCP192.168.100.0段的,usg接口的网关地址是192.168.100.1,防火墙策略启用192.168.100.0段到wan1段的内网和外网的NAT地址转换。 2…...
VTK知识学习(33)-交互问题2
1、前言 主要是针对前面有过实现不了交互的情况进行说明,经过一些尝试和分析调用API,总算实现RenderWindowControl函数回调正常串接,当然这个移动处理事件的效果目前也没有确认。 2、使用 vtkImageReslice reslice vtkImageReslice.New();p…...
c++ thread线程join、detach、joinable方法
(621条消息) 线程中断Thread的interrupt()方法_thread interrupt_萝卜阿咕咕的博客-CSDN博客 C/C编程:std::thread 详解-CSDN博客 #include <iostream> #include <thread>void do_some_work() {std::cout<<"Hello Concurrent World\n"…...
Transformer:深度学习的变革力量
深度学习领域的发展日新月异,在自然语言处理(NLP)、计算机视觉等领域取得了巨大突破。然而,早期的循环神经网络(RNN)在处理长序列时面临着梯度消失、并行计算能力不足等瓶颈。而 Transformer 的横空出世&am…...
【Python】__main__.py、__init__.py
文章目录 1. __init__.py作用:用法:示例:特点 2. __main__.py作用:用法:示例:特点: 3. 综合示例总结: 1. init.py 作用: __init__.py 文件的主要作用是标识一个目录是一…...
springboot集成整合工作流,activiti审批流,整合实际案例,流程图设计,流程自定义,表单配置自定义,代码demo流程
前言 activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,一套完整并且实际运用在多套项目中的案例,满足日常业务流程审批需求。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器,流行的前后端…...
代码随想录算法【Day16】
Day16 513.找二叉树左下角的值 本题使用迭代法更简单,使用迭代法和递归法的区别是什么 递归法 目标就是找深度最大的叶子结点 无论前中后序遍历,都是左节点先被遍历到,所以一旦得到深度最大的节点,就是最后一行最靠左侧的节点…...
从光子到图像——相机如何捕获世界?
引言 你是否想过为何我们按一下相机快门就可以将眼前广袤多彩的世界显示于一个小小的相机屏幕上?本期推文中将带着大家重现从光子转换为电子、电子转换为图像中数字驱动值的整个流程。 ▲人们通过相机捕获眼前的场景 从光子到电子的转换 光线首先通过光学镜头进入相…...
Harmony开发【笔记1】报错解决(字段名写错了。。)
在利用axios从网络接收请求时,发现返回obj的code为“-1”,非常不解,利用console.log测试,更加不解,可知抛出错误是 “ E 其他错误: userName required”。但是我在测试时,它并没有体现为空,…...
Ubuntu 下载安装 elasticsearch7.17.9
参考 https://blog.csdn.net/qq_26039331/article/details/115024218 https://blog.csdn.net/mengo1234/article/details/104989382 过程 来到 Es 的版本发布列表页面:https://www.elastic.co/downloads/past-releases#elasticsearch 根据自己的系统以及要安装的…...
8. LINUX 用户和组
文章目录 8.1 密码文件:/etc/passwd1. 登录名(Login Name)2. 经过加密的密码(Encrypted Password)3. 用户 ID(User ID, UID)4. 组 ID(Group ID, GID)5. 注释(…...
vue监听中的watch监听(详解)
1、watch 选项用于监听数据的变化并执行相应的回调函数。watch 选项提供了两个重要的属性:deep 和 immediate。1.1、深度监听 (deep: true) 当你需要监听一个对象或数组内部的变化时,可以使用 deep: true。 这会使得 watch 监听器递归地监听对象或数组内…...
微信小程序中 隐藏scroll-view 滚动条 网页中隐藏滚动条
在微信小程序中隐藏scroll-view的滚动条可以通过以下几种方法实现: 方法一:使用CSS隐藏滚动条 在小程序的样式文件中(如app.wxss或页面的.wxss文件),添加以下CSS代码来隐藏滚动条: scroll-view ::-webkit…...
K8s Pod OOMKilled,监控却显示内存资源并未打满
1. 问题现象 pod一直重启,通过grafana查看,发现内存使用率并没有100%。 2. 排查过程 2.1 describe查看pod最新一次的状态 可以明显看到,最近一次的重启就是因为内存不足导致的。 2.2 describe 查看node节点状态 找到原因了,原来…...
对话|全年HUD前装将超330万台,疆程技术瞄准人机交互“第一屏”
2024年,在高阶智驾进入快速上车的同时,座舱人机交互也在迎来新的增长点。Chat GPT、AR-HUD、车载投影等新配置都在带来新增量机会。 高工智能汽车研究院监测数据显示,2024年1-10月,中国市场(不含进出口)乘用…...
【HTML+CSS+JS+VUE】web前端教程-10-列表标签之无序列表
无序列表实现 无序列表是一个项目的列表,此列项目使用粗体圆点(典型的小黑圆圈)进行标记 无序列表始于<ul>标签,每个列表项始于<li>标签。<ul><li>苹果...
基于V2X的无人机与特种车辆战地智能通信:技术融合与实战应用
一、引言 1.1 研究背景与意义 在现代战争的复杂环境中,通信系统的高效与可靠已然成为决定胜负的关键因素。随着军事技术的飞速发展,战争形态发生了深刻变革,作战空间不断拓展,从陆地、海洋、天空延伸至电磁、网络、太空等多维领…...
20250109下载JDK17的方法链接
20250109下载JDK17的方法&链接 2025/1/9 16:20 缘起:编译地面站应用程序QGC,需要安装QT和【旧版本的】JDK17。 当时在网上没有找到JDK17,就安装了比较接近的JDK21。反正最后的QT for Android最后就是没有编译通过。 到底是谁的问题&#…...
杭州铭师堂的云原生升级实践
作者:升学e网通研发部基建团队 公司介绍 杭州铭师堂,是一个致力于为人的全面发展而服务的在线教育品牌。杭州铭师堂秉持“用互联网改变教育,让中国人都有好书读”的使命,致力于用“互联网教育”的科技手段让更多的孩子都能享有优…...
chrome浏览器的更新提示弹窗无法更新Chrome解决方法
使用组策略编辑器 此方法适用于 Windows 系统且系统为专业版及以上版本,家庭版系统没有组策略功能。 按下Win R键,打开 “运行” 对话框,输入gpedit.msc并回车,打开组策略编辑器。 在组策略编辑器中,依次展开 “计算机…...
LLM prompt提示构造案例:语音回复内容;o1思维链
1、语音回复内容 目的: 语音聊天助手的prompt,让大模型来引导聊天内容,简短和友好,从而文字转语音时候也比较高效。 ## 角色设定与交互规则 ### 基本角色 你是用户的好朋友. 你的回答将通过逼真的文字转语音技术阅读. ### 回答规则…...
OceanBase 学习计划全攻略:开启分布式数据库探索之旅
《OceanBase 学习计划全攻略:开启分布式数据库探索之旅》 在当今数字化浪潮汹涌澎湃的时代,数据库作为企业信息存储与管理的核心基础设施,其性能、可靠性和扩展性至关重要。OceanBase 作为一款具有卓越分布式特性的国产数据库,正…...
Linux 虚拟机与windows主机之间的文件传输--设置共享文件夹方式
Linux 虚拟机与windows主机之间的文件传输 设置共享文件夹方式 在虚拟机中打开终端查看是否已经新建完成,到文件夹中找到它看一下,这个位置就能存储东西啦...
React Context用法总结
1. 基本概念 1.1 什么是 Context Context 提供了一种在组件树中共享数据的方式,而不必通过 props 显式地逐层传递。它主要用于共享那些对于组件树中许多组件来说是"全局"的数据。 1.2 基本用法 // 1. 创建 Context const ThemeContext React.createC…...
Linux好用软件
力荐软件 apt-fast:更快速的软件管理安装过程会进入一个图形界面,配置线程数等信息,全部默认即可 sudo add-apt-repository ppa:apt-fast/stable sudo apt-get update sudo apt-get -y install apt-fast 以后安装应用,把apt-get直接替换成apt-fast即可,例如安装vlc sudo…...
【MYSQL】
文章目录 1.DDL 1.DDL --添加字段 ALTER TABLE table_name add COLUMN embed_model VARCHAR(32) NOT NULL COMMENT 名称备注 COLLATE utf8mb4_bin AFTER config_code;--修改字段 ALTER TABLE table_name CHANGE COLUMN column_a column_b VARCHAR(500) NOT NULL COMMENT 配置信…...
webrtc之rtc::ArrayView<const uint8_t>
rtc::ArrayView<const uint8_t> 是 WebRTC(或其他基于 rtc 命名空间的库)中常见的一个类型,它通常用于表示一块 只读的内存区域,该内存区域由一系列 uint8_t 类型(无符号 8 位整数)元素组成。 1. rt…...
深入理解 MySQL 的 EXPLAIN 工具
1. 什么是 EXPLAIN 工具? EXPLAIN 是 MySQL 中用来分析 SQL 查询执行计划的命令,它能够显示查询在执行时会如何访问表、使用哪些索引、扫描多少行等信息。通过 EXPLAIN 工具,开发者可以直观地了解查询的执行过程,从而进行针对性的…...
谷歌Google、紫鸟浏览器插件开发
对于跨境电商行业的IT部门来说,经常需要获取各种店铺相关数据,但是仅靠官方提供的接口来获取数据远远不够,这个时候我们就需要插件或者RPA的方式来获取数据。 以下是关于自研紫鸟插件的简单demo,紫鸟浏览器使用的是火狐和谷歌的插…...
HTML 显示器纯色亮点检测工具
HTML 显示器纯色亮点检测工具 相关资源文件已经打包成html等文件,可双击直接运行程序,且文章末尾已附上相关源码,以供大家学习交流,博主主页还有更多Html相关程序案例,秉着开源精神的想法,望大家喜欢&#…...
Win32汇编学习笔记09.SEH和反调试
Win32汇编学习笔记09.SEH和反调试-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net SEH - structed exception handler 结构化异常处理 跟筛选一样都是用来处理异常的,但不同的是 筛选器是整个进程最终处理异常的函数,但无法做到比较精细的去处理异常(例如处理…...
计算机组成原理(九):乘法器
乘法器原理 乘法器的工作原理可以用二进制乘法来说明。二进制乘法和十进制乘法类似,通过部分积的累加得到结果。 部分积的生成 在二进制乘法中,每一位的乘积是两个二进制数位的 与运算(0 0 0,1 0 0,0 1 0&…...
前端开发中常用的插件库
ahooks 一个高质量且可靠的React Hooks库。class-variance-authority(CVA) 是一个专注于解决CSS类管理问题的库。classnames 是一个在React开发中非常流行的JavaScript工具库,它可以帮助开发者有条件地连接类名字符串。copy-to-clipboard 一个…...
认识+安装ElasticSearch
1. 为什么要学习ElasticSearch? 一般的来说,项目中的搜索功能尤其是电商项目,商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的,存在很多问题。 1.1 数据库搜索所存在的问题 1.1.1 查询效率较低 由于数据库模糊查询不走索引&…...
Nginx | 解决 Spring Boot 与 Nginx 中的 “413 Request Entity Too Large“ 错误
关注:CodingTechWork 引言 在 Web 开发中,413 Request Entity Too Large 是一种常见的 HTTP 错误,它指示客户端请求的实体(例如文件或数据)超出了服务器允许的最大大小。对于使用 Spring Boot 和 Nginx 的应用程序来说…...
CAD批量打印可检索的PDF文件
本文虽介绍CAD使用方法,但还是劝告大家尽早放弃使用CAD软件。。。。太TM难用了 当你打开CAD时发现如下一堆图纸,但是不想一个一个打印时。你可以按照下面操作实现自动识别图框实现批量打印。 1.安装批量打印插件 2.安装后打开CAD,输入命令Bp…...
理解Unity脚本编译过程:程序集
https://docs.unity3d.com/Manual/script-compilation.html 关于Unity C#脚本编译的细节,其中一个比较重要的知识点就是如何自定义Assembly。 预定义的assembly 默认情况下,Unity会按照这个规则进行编译。 PhaseAssembly nameScript files1Assembly-…...
Linux-Ubuntu之SPI串行通信陀螺仪和加速度计
Linux-Ubuntu之SPI串口通信陀螺仪和加速度计 一,SPI通信原理二,ICM-20608六轴传感器控制三,代码1.小tip 一,SPI通信原理 SPI:串行全双工通信,最高能达到百MHZ,通常一个主设备跟多个从设备&…...
【C++/控制台】2048小游戏
源代码: #include <iostream> #include <windows.h> #include <stdio.h> #include <math.h> #include <stdlib.h> #include <conio.h> #include <time.h>// #define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME)…...
github gitbook写书
github创建新的仓库 在仓库中添加目录 ‘SUMMARY.md # Summary * [简介](README.md)gitbook 新建一个site https://www.gitbook.com/ 注册账号 取名字 一路 next,注意选免费版 最后 gitbook同步到github 你在主页可以看到 刚刚的test网站 点击右上角圈出来…...
鸿蒙中调整应用内文字大小
1、ui Stack() {Row() {ForEach([1, 2, 3, 4], (item: number) > {Text().width(3).height(20).backgroundColor(Color.Black).margin(item 2 ? { left: 8 } : item 3 ? { left: 7 } : { left: 0 })})}.width(97%).justifyContent(FlexAlign.SpaceBetween).padding({ ri…...
欧拉公式和傅里叶变换
注:英文引文机翻,未校。 中文引文未整理去重,如有异常,请看原文。 Euler’s Formula and Fourier Transform Posted byczxttkl October 7, 2018 Euler’s formula states that e i x cos x i sin x e^{ix} \cos{x} i …...
【C++经典例题】求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏: 期待您的关注 题目描述: 原题链接: 求123...n_牛客题霸_牛客网 (nowcoder.com) 解题思路: …...
Robot---奇思妙想轮足机器人
1 背景 传统机器人有足式、轮式、履带式三种移动方式,每种移动方式都有各自的优缺点。轮式机器人依靠车轮在地面上移动,能源利用率高、移动速度快,但是仅以轮子与地面接触,缺乏越障能力和对复杂地形的适应能力,尤其面对…...
升级 Spring Boot 3 配置讲解 —— 如何处理文件上传下载?
学会这款 🔥全新设计的 Java 脚手架 ,从此面试不再怕! 1. 环境准备 在开始之前,确保你已经具备以下环境: JDK 17 或更高版本(Spring Boot 3 要求的最低 JDK 版本)Maven 或 Gradle 构建工具Spr…...
(四)结合代码初步理解帧缓存(Frame Buffer)概念
帧缓存(Framebuffer)是图形渲染管线中的一个非常重要的概念,它用于存储渲染过程中产生的像素数据,并最终输出到显示器上。简单来说,帧缓存就是计算机图形中的“临时画布”,它储存渲染操作生成的图像数据&am…...