在现代软件开发中,性能是一个永恒的话题。特别是在处理网络请求、文件读写等 I/O 密集型任务时,传统的同步编程模型可能会因为等待而浪费大量时间。为了解决这个问题,异步编程应运而生。Python 通过内置的 asyncio 库,为开发者提供了强大而优雅的异步编程能力。 [1][2]

本文将带你从零开始,逐步深入 asyncio 的世界,理解其核心概念,并最终通过实战案例掌握其用法。

1. 什么是异步编程?为什么要用它?

想象一下你在厨房做饭,需要同时烧水、切菜和炒菜。

  • 同步 (Synchronous):你先把水壶放到灶上,然后就一直盯着它,直到水烧开。之后,你再去切菜,切完所有菜后,最后才开始炒菜。在这个过程中,当你在等待水烧开时,你什么也做不了,时间被白白浪费。
  • 异步 (Asynchronous):你把水壶放到灶上后,就不管它了,直接去切菜。切菜的间隙,你抽空看一眼水开了没。水一开,你就去处理。这样,等待水烧开的时间被你用来切菜,整个做饭的效率大大提高。 [3]

代码的世界也是如此。同步编程就是一次只做一件事,必须等前一件事(比如一次网络请求)完成后才能做下一件。 [1] 而异步编程允许程序在等待一个耗时操作(通常是 I/O 操作)时,切换去执行其他任务,从而提高整体效率。 [1][3]

asyncio 正是 Python 用于实现这种高效工作模式的标准库,它特别适合 I/O 密集型和高层级的网络代码。 [3]

2. asyncio 的核心基石:async/await 与协程

要使用 asyncio,首先需要理解几个关键概念。

协程 (Coroutine)

在 Python 中,使用 async def 关键字定义的函数,我们称之为协程函数。调用它并不会立即执行函数体,而是会返回一个协程对象。 [4] 协程可以被看作是一种可以暂停和恢复执行的特殊函数。 [1]

await

这个关键字只能在 async def 函数内部使用。它的作用是“等待”一个可等待对象 (Awaitable) 执行完成。 [5] 可等待对象包括协程、任务 (Task) 和 Future 对象。 [5] 当程序执行到 await 时,它会告诉事件循环:“这个操作有点耗时,我先在这里暂停,你可以去忙别的,等我好了再回来继续。” [6]

事件循环 (Event Loop)

事件循环是 asyncio 的心脏。 [2][6] 你可以把它想象成一个大管家,负责调度和执行所有的异步任务。它会不断检查是否有任务已经准备好可以继续运行,或者是否有新的任务需要开始。 [2]

asyncio.run()

这是启动异步程序的入口。它会创建一个新的事件循环,运行你传入的顶级协程(通常是 main 函数),并在协程执行完毕后关闭事件循环。 [7][8]

3. 牛刀小试:你的第一个 asyncio 程序

让我们来看一个最简单的例子。

import asyncio
import time# 使用 async def 定义一个协程函数
async def say_hello(delay, message):"""一个简单的协程,会延迟指定秒数后打印消息。"""print(f"[{time.strftime('%X')}] 开始任务: {message}")# asyncio.sleep 是一个异步的 time.sleep()# 当遇到 await asyncio.sleep() 时,事件循环会切换到其他任务await asyncio.sleep(delay)print(f"[{time.strftime('%X')}] 完成任务: {message}")# 定义主入口协程
async def main():print(f"[{time.strftime('%X')}] 程序开始")# 直接 await 调用协程await say_hello(2, "你好")await say_hello(1, "世界")print(f"[{time.strftime('%X')}] 程序结束")# 使用 asyncio.run() 启动程序
if __name__ == "__main__":asyncio.run(main())

运行结果分析:

[13:30:00] 程序开始
[13:30:00] 开始任务: 你好
[13:30:02] 完成任务: 你好
[13:30:02] 开始任务: 世界
[13:30:03] 完成任务: 世界
[13:30:03] 程序结束

你会发现,这段代码虽然是异步的,但执行顺序和同步代码一样,总共耗时 3 秒。这是因为我们依次 await 了两个协程,必须等第一个完成后,第二个才会开始。

那么,如何让它们“同时”运行呢?

4. 并发执行:asyncio.gatherasyncio.create_task

为了真正实现并发,我们需要让多个任务在事件循环中同时被调度。 [3]

asyncio.gather

asyncio.gather() 可以接收一个或多个可等待对象,将它们并发执行,并按输入顺序返回所有结果。 [9]

修改上面的 main 函数:

async def main():print(f"[{time.strftime('%X')}] 程序开始")# 使用 asyncio.gather 并发运行两个协程await asyncio.gather(say_hello(2, "你好"),say_hello(1, "世界"))print(f"[{time.strftime('%X')}] 程序结束")# ... 其他代码不变 ...

新的运行结果:

[13:32:10] 程序开始
[13:32:10] 开始任务: 你好
[13:32:10] 开始任务: 世界
[13:32:11] 完成任务: 世界
[13:32:12] 完成任务: 你好
[13:32:12] 程序结束

观察时间戳,两个任务几乎是同时开始的。耗时1秒的任务先结束,耗时2秒的后结束。整个程序的总耗时取决于最长的那个任务,也就是 2 秒,而不是之前的 3 秒。这就是并发带来的效率提升! [3]

asyncio.create_task

asyncio.create_task() 用于将一个协程包装成一个任务 (Task),并提交给事件循环立即开始执行,而不需要马上 await 它。 [6][7] TaskFuture 的一个子类,专门用于管理协程。 [4]

这就像是“发射后不管”(fire-and-forget),你创建了一个任务让它在后台运行,然后可以继续做其他事情。 [6]

async def main():print(f"[{time.strftime('%X')}] 程序开始")# 创建任务,任务会立即开始在事件循环中被调度task1 = asyncio.create_task(say_hello(2, "你好"))task2 = asyncio.create_task(say_hello(1, "世界"))print(f"[{time.strftime('%X')}] 任务已创建")# 在这里可以做其他事情await asyncio.sleep(0.5)print(f"[{time.strftime('%X')}] 主程序做了一些其他工作")# 等待任务完成await task1await task2print(f"[{time.strftime('%X')}] 程序结束")

运行结果:

[13:35:20] 程序开始
[13:35:20] 任务已创建
[13:35:20] 开始任务: 你好
[13:35:20] 开始任务: 世界
[13:35:20] 主程序做了一些其他工作
[13:35:21] 完成任务: 世界
[13:35:22] 完成任务: 你好
[13:35:22] 程序结束

create_taskgather 的区别在于控制的粒度。gather 是一种更高级的抽象,适合一次性并发运行多个任务并收集结果的场景。 [9] create_task 则提供了更灵活的控制,允许你在任务运行期间执行其他逻辑。 [6][9]

5. 实战演练:使用 aiohttp 并发下载网页

理论讲了这么多,让我们来看一个最能体现 asyncio 价值的场景:并发网络请求。我们将使用流行的异步 HTTP 客户端库 aiohttp。 [10][11]

首先,你需要安装 aiohttp
pip install aiohttp

下面的例子将对比同步和异步方式获取多个网页标题所花费的时间。

import asyncio
import time
import aiohttp
import requests  # 用于同步对比urls = ['https://www.python.org','https://github.com','https://www.wikipedia.org','https://www.youtube.com','https://www.amazon.com',
]def get_title_sync(url):"""同步获取网页标题"""try:resp = requests.get(url, timeout=10)# 一个简单的解析,实际应用中建议使用 BeautifulSoupreturn resp.text.split('<title>')[1].split('</title>')[0].strip()except Exception as e:return f"Error: {e}"async def get_title_async(session, url):"""异步获取网页标题"""try:# aiohttp 使用 session.get() 发起请求async with session.get(url, timeout=10) as resp:# resp.text() 是一个协程,需要 awaithtml = await resp.text()return html.split('<title>')[1].split('</title>')[0].strip()except Exception as e:return f"Error: {e}"async def main_async():# aiohttp 建议使用一个 ClientSession 来执行所有请求async with aiohttp.ClientSession() as session:tasks = [get_title_async(session, url) for url in urls]# 使用 gather 并发执行所有任务titles = await asyncio.gather(*tasks)for url, title in zip(urls, titles):print(f"{url}: {title}")if __name__ == "__main__":# --- 同步版本 ---print("--- 开始同步请求 ---")start_time_sync = time.time()for url in urls:title = get_title_sync(url)print(f"{url}: {title}")end_time_sync = time.time()print(f"同步请求总耗时: {end_time_sync - start_time_sync:.2f} 秒\n")# --- 异步版本 ---print("--- 开始异步请求 ---")start_time_async = time.time()asyncio.run(main_async())end_time_async = time.time()print(f"异步请求总耗时: {end_time_async - start_time_async:.2f} 秒")

典型的运行结果:

--- 开始同步请求 ---
https://www.python.org: Welcome to Python.org
https://github.com: GitHub: Let’s build from here
https://www.wikipedia.org: Wikipedia
https://www.youtube.com: YouTube
https://www.amazon.com: Amazon.com. Spend less. Smile more.
同步请求总耗时: 4.58 秒--- 开始异步请求 ---
https://www.python.org: Welcome to Python.org
https://github.com: GitHub: Let’s build from here
https://www.wikipedia.org: Wikipedia
https://www.youtube.com: YouTube
https://www.amazon.com: Amazon.com. Spend less. Smile more.
异步请求总耗时: 0.95 秒

结果一目了然。异步版本的速度比同步版本快了数倍。 [5] 这是因为 asyncio 在等待一个网站响应时,没有闲着,而是立即去请求下一个网站,极大地利用了网络 I/O 的等待时间。 [11]

6. 进阶:协程间的同步与通信

当我们有多个协程并发运行时,有时它们需要访问同一个资源,或者需要相互传递工作任务。这时,为了避免数据混乱和协调工作流程,就需要用到同步和通信机制。asyncio 提供了与多线程编程中类似的工具,但它们是为协程专门设计的。

6.1 资源保护:asyncio.Lock

在并发环境中,如果多个任务同时尝试修改一个共享资源(例如一个变量或文件),就可能导致竞争条件 (Race Condition),使得最终结果不可预测。

虽然 asyncio 在单线程上运行,不会有真正的并行执行,但一个协程可以在 await 处被挂起,此时事件循环会运行另一个协程。如果这两个协程都在修改同一个数据,问题依然存在。

asyncio.Lock 就是用来解决这个问题的。它保证在任何时候,只有一个协程能够获得锁并执行“临界区”代码。

使用场景:保护对共享资源的访问,确保操作的原子性。

让我们看一个例子:多个协程同时增加一个共享计数器。

import asyncio# 一个共享的资源
shared_counter = 0async def unsafe_worker():"""一个没有锁保护的协程"""global shared_counter# 1. 读取当前值current_value = shared_counter# 在这里,协程可能会被挂起,切换到另一个 workerawait asyncio.sleep(0.01) # 2. 基于旧值计算新值new_value = current_value + 1# 3. 写入新值shared_counter = new_valueasync def safe_worker(lock):"""一个有锁保护的协程"""global shared_counter# 使用 async with lock 语法可以自动获取和释放锁async with lock:current_value = shared_counterawait asyncio.sleep(0.01)new_value = current_value + 1shared_counter = new_valueasync def main():global shared_counter# --- 演示不安全的情况 ---print("--- 演示不安全的情况 ---")shared_counter = 0tasks_unsafe = [unsafe_worker() for _ in range(100)]await asyncio.gather(*tasks_unsafe)print(f"没有锁保护,100个任务完成后的计数器值: {shared_counter}") # 结果通常远小于100# --- 演示安全的情况 ---print("\n--- 演示安全的情况 ---")shared_counter = 0lock = asyncio.Lock()tasks_safe = [safe_worker(lock) for _ in range(100)]await asyncio.gather(*tasks_safe)print(f"使用锁保护,100个任务完成后的计数器值: {shared_counter}") # 结果总是100if __name__ == "__main__":asyncio.run(main())

代码解读与结果分析:

  • unsafe_worker: 在读取 (current_value = ...) 和写入 (shared_counter = ...) 之间有一个 await。这给了事件循环切换到另一个 unsafe_worker 的机会。多个 worker 可能会基于同一个旧值进行计算,导致一些增加操作丢失。因此,最终结果会小于 100。
  • safe_worker: 使用了 async with lock:。当一个协程进入这个代码块时,它会获取锁。如果此时其他协程也想进入,它们必须 await,直到第一个协程执行完毕并自动释放锁。这确保了“读-改-写”这个操作的完整性,所以最终结果总是正确的 100。

6.2 任务分发:asyncio.Queue

asyncio.Queue 是一个为异步编程设计的队列,它非常适合经典的生产者-消费者 (Producer-Consumer) 模型。

  • 生产者 (Producer):创建任务或数据,并将其放入队列。
  • 消费者 (Consumer):从队列中取出任务或数据,并进行处理。

队列本身处理了所有的同步逻辑:

  • 如果消费者试图从空队列中获取 (get) 数据,它会自动 await,直到队列中有新数据。
  • 如果生产者试图向一个已满的队列(如果创建时指定了 maxsize)中放入 (put) 数据,它会自动 await,直到队列有空位。

使用场景:解耦任务的创建和执行,实现任务分发系统,控制并发处理任务的数量。

让我们构建一个简单的爬虫模型:一个生产者负责发现 URL 并放入队列,多个消费者负责从队列中取出 URL 并“下载”。

import asyncio
import randomasync def producer(queue, num_urls):"""生产者:生成一些模拟的URL并放入队列"""print("生产者启动...")for i in range(num_urls):url = f"https://example.com/page/{i}"# 模拟发现URL需要一些时间await asyncio.sleep(random.uniform(0.1, 0.5))# 将URL放入队列await queue.put(url)print(f"生产者放入: {url}")print("生产者完成任务。")async def consumer(name, queue):"""消费者:从队列中获取URL并处理"""print(f"消费者 {name} 启动...")# 持续从队列中获取任务while True:# 从队列中获取URL,如果队列为空,会在此处等待url = await queue.get()print(f"消费者 {name} 正在处理: {url}")# 模拟处理任务需要的时间await asyncio.sleep(random.uniform(0.5, 1.5))print(f"消费者 {name} 完成处理: {url}")# 必须调用 task_done() 来通知队列这个任务已经处理完毕queue.task_done()async def main():# 创建一个不限大小的队列task_queue = asyncio.Queue()num_urls_to_produce = 10num_consumers = 3# 启动生产者producer_task = asyncio.create_task(producer(task_queue, num_urls_to_produce))# 启动多个消费者consumer_tasks = []for i in range(num_consumers):task = asyncio.create_task(consumer(f"C{i+1}", task_queue))consumer_tasks.append(task)# 等待生产者完成所有URL的放入await producer_taskprint("所有URL已放入队列,等待消费者处理...")# 等待队列中的所有任务都被处理完毕# queue.join() 会阻塞,直到队列中每个项目的 task_done() 都被调用await task_queue.join()print("所有任务处理完毕!")# 所有任务都处理完了,消费者们还在 while True 循环里等待新任务# 为了让程序能正常退出,我们需要取消这些消费者任务for task in consumer_tasks:task.cancel()if __name__ == "__main__":asyncio.run(main())

代码解读与关键点:

  1. queue.put(item): 生产者使用它来异步地添加项目。
  2. queue.get(): 消费者使用它来异步地获取项目。这是主要的同步点。
  3. queue.task_done(): 这是至关重要的一步!消费者处理完一个项目后,必须调用此方法。它会减少队列的内部计数器。
  4. queue.join(): main 函数用它来等待所有项目都被处理。它会一直阻塞,直到队列的内部计数器归零。这确保了我们在程序结束前,所有工作都已完成。
  5. 任务取消: 因为消费者通常在一个无限循环中工作,当所有工作完成后,我们需要显式地取消它们,否则 asyncio.run(main()) 将永远不会退出。

7. 总结

asyncio 为 Python 带来了强大的并发能力,是构建高性能网络应用和服务的利器。

核心要点回顾:

  • 适用场景:I/O 密集型任务(如网络爬虫、Web 服务器、数据库连接等)。
  • 核心语法async def 定义协程,await 暂停协程并等待结果。
  • 启动方式asyncio.run() 是现代 Python 中启动异步程序的标准方式。
  • 并发执行:使用 asyncio.gather()asyncio.create_task() 来并发运行多个任务。
  • 同步与通信:使用 asyncio.Lock 保护共享资源,避免竞争条件;使用 asyncio.Queue 构建生产者-消费者模型,高效地分发和处理任务。
  • 生态系统:需要配合 aiohttp, aiodns, asyncpg 等异步库才能发挥最大威力。

从 Python 3.4 首次引入 asyncio 至今,它已经变得越来越成熟和易用。虽然异步编程的思维方式需要一些时间来适应,但一旦你掌握了它,它将成为你工具箱中应对高并发挑战的一把“瑞士军刀”。希望这篇博客能为你打开异步编程的大门。


参考文章

  1. asyncio 教程- 什么是异步? - Graia 官方文档
  2. Python asyncio 模块 - 菜鸟教程
  3. Asyncio in Python: A Comprehensive Guide with Examples. | by Obafemi - Medium
  4. 使用asyncio - python并发编程-中文版
  5. Python asyncio 從不會到上路 - MyApollo
  6. Solve Common Asynchronous Scenarios With Python’s “asyncio” - Better Programming
  7. Coroutines and Tasks — Python 3.13.5 documentation
  8. 使用asyncio - Python教程- 廖雪峰的官方网站
  9. Is it more efficient to use create_task(), or gather()? - Stack Overflow
  10. python asyncio 异步I/O - 实现并发http请求(asyncio + aiohttp) - yuminhu - 博客园
  11. python asyncio 异步I/O - 实现并发http请求(asyncio + aiohttp) - 上海-悠悠- 博客园
  12. asyncio教程原创 - CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV颜色矩哈希算法------cv::img_hash::ColorMomentHash

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该类实现了颜色矩哈希算法&#xff08;Color Moment Hash&#xff09;&#xff0c;用于图像相似性比较。它基于图像在HSV颜色空间中的颜色矩统计特…

上海交大医学院张维拓老师赴同济医院做R语言训练营培训

当前&#xff0c;医学与人工智能的深度融合正迎来历史性发展机遇。华中科技大学同济医学院附属同济医院&#xff08;以下简称“同济医院”&#xff09;作为医疗人工智能应用的先行探索者&#xff0c;已在电子病历辅助书写、科研数据分析、医疗合同自动化审核等关键场景完成试点…

使用阿里云/腾讯云安装完成mysql使用不了

显示错误1130 - Host 106.228.110.117 is not allowed to connect to this MySQL server解决方案进入服务器的mysql命令行mysql -u root -p查看数据库SHOW DATABASES;选择mysql数据库USE mysql;查看里面的表SHOW TABLES;查询user表格的权限限制SELECT Host, User FROM user;将权…

第35周—————糖尿病预测模型优化探索

目录 目录 前言 1.检查GPU 2.查看数据 ​编辑 3.划分数据集 4.创建模型与编译训练 5.编译及训练模型 6.结果可视化 7.总结 前言 &#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 1.检查GPU import torch.n…

接口(上篇)

接口&#xff08;上篇&#xff09;1.概念2.语法规则3.使用和特性4.实现多接口5.接口间继承1.概念 接口就是公共的行为规范标准&#xff0c;大家在实现时&#xff0c; 只要符合规范标准&#xff0c;就可以通用。 在Java中&#xff0c;接口可以看成是&#xff1a;多个类的公共规…

UE5 源码编译setup.bat报错

文章目录编译报错改动说明小结更新编译报错 改动说明 因为需要整服务器&#xff0c;就编译源码&#xff0c;然后就遇到这个&#xff0c;很无语。这个问题一直存在&#xff0c;UE官方也不修复&#xff0c;也算是修复了&#xff0c;只是每次都要去重新下载替换下。也可以去问问d…

Linux下PCIe子系统(二)——PCIe子系统框架详解

Linux下PCIe子系统&#xff08;二&#xff09;——PCIe子系统框架详解 1. 概述 PCIe&#xff08;PCI Express&#xff09;子系统是Linux内核中负责管理PCI/PCIe设备的核心组件。它提供了一套完整的框架来发现、配置和管理PCI设备&#xff0c;实现了设备的即插即用和热插拔功能。…

[特殊字符] LLM(大型语言模型):智能时代的语言引擎与通用推理基座

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 从千亿参数到人类认知的AI革命 &#x1f50d; 一、核心定义与核心特征…

18-C#改变形参内容

C#改变形参内容 1.ref 参数 int A100; add1(ref A) public int add1 (ref int x) {x x 10;return x; }2.out 参数 int A100; int B200; int Z; add3(A,B, out Z) public int add3 (int x&#xff0c;int y&#xff0c;int z) {z x y;return z; }

恒盾C#混淆加密大师最新版本1.4.0更新 - 增强各类加密效果, 提升兼容性, 使.NET加密更简单

C#/.NET作为托管语言, 其编译生成的EXE/DLL极易被反编译工具还原源码。据统计&#xff0c;大量的商业软件曾遭遇过代码逆向风险&#xff0c;导致核心算法泄露、授权被跳过. 因此对于C#语言开发的.NET程序来说, 在发布前进行混淆和加密非常有必要. 恒盾C#混淆加密大师作为一款.N…

数学建模:非线性规划:二次规划问题

一、定义如果规划模型的目标函数是决策向量的二次函数&#xff0c;约束条件都是线性的&#xff0c;那么这个模型称为二次规划&#xff08;QP&#xff09;模型。二次规划模型的一般形式为二、性质凸性判定准则二次规划问题的凸性完全由Hessian矩阵H决定&#xff1a;​​严格凸QP…

4. 那在详细说一下 http 2.0 的特点

总结 二进制协议&#xff1a;文本通信改为二进制帧通信&#xff0c;数据可以划分为更小的帧&#xff0c;便于高效解析和传输。多路复用&#xff1a;废除 pipeline 管道&#xff0c;避免了“队头阻塞”问题。允许同一个 TCP 连接同时发送多个请求和协议&#xff0c;提高网络资源…

Qt中遍历QMap的多种方法及性能分析

Qt中遍历QMap的多种方法及性能分析遍历QMap的方法**1、使用迭代器&#xff08;STL风格&#xff09;****2、使用Java风格迭代器****3、使用C11范围循环****4、使用键值分离遍历**性能分析使用建议遍历QMap的方法 1、使用迭代器&#xff08;STL风格&#xff09; QMap<QStrin…

Unity3D物理引擎性能优化策略

前言 在Unity3D中优化物理引擎性能&#xff0c;尤其是处理3D碰撞器与2D碰撞器的映射问题&#xff0c;需要结合系统特性和最佳实践。以下是关键策略和实现方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击进来一起交流一下开发经验呀…

集群与集群应用

负载均衡与高可用综合实验一、集群是什么&#xff1f;是有一组独立的计算机系统构成的一个松耦合的多处理系统&#xff0c;作为一个整体向用户提供一组网络资源&#xff0c;这些单个的计算机就是集群的节点。二、集群类型Load Balance cluster&#xff08;负载均衡集群&#xf…

jmm,`as - if - serial` 与 `happens - before` 原则

在Java并发编程中&#xff0c;as - if - serial 与 happens - before 原则是确保程序在多线程环境下正确执行的重要规则&#xff0c;下面为你详细讲解&#xff1a; as - if - serial原则 定义&#xff1a;as - if - serial 原则是指&#xff0c;不管编译器和处理器如何优化&…

主流大模型Agent框架 AutoGPT详解

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《GPT多模态大模型与AI Agent智能体》&#xff08;跟我一起学人工智能&#xff09;【陈敬雷编著】【清华大学出版社】 GPT多模态大模型与AI Agent智能体书籍本章配套视频课程【陈敬雷】 文…

kotlin学习,val使用get()的问题

疑问&#xff1a;定义val怎么还能使用get()代码示例&#xff1a;private val nametype:Intget()Business.carInfo?.let{carSc(it)}?:LType.AS回答&#xff1a;Kotlin 允许为属性定义自定义 getter&#xff0c;每次访问属性时会执行该方法疑问&#xff1a;这里引出另一个不解&…

解决el-select数据类型相同但是显示数字的问题

这个不是我写的&#xff0c;只是遇到的bug&#xff0c;写法问题&#xff0c;忽略了值的绑定的问题源代码bug&#xff1a;<el-selectv-model"schemeInfo.horizon"placeholder"请选择起报月份"clearablefilterable><el-option v-for"(option,i…

熟练掌握RabbitMQ和Kafka的使用及相关应用场景。异步通知与解耦,流量削峰,配合本地消息表实现事务的最终一致性并解决消息可靠、顺序消费和错误重试等问题

RabbitMQstock.#.nyse &#xff0c;#匹配多个字符&#xff0c;*匹配一个字符。 Confirm Callback 到达exchange的回调。 Return Callback 到达queue失败的回调。 Kafka Kafka生产端分区器&#xff1a; 1.直接指定partition 指定0,1。 2.设置hashkey&#xff0c;计算key的hash值…