当前位置: 首页 > news >正文

多线程、多进程,还是异步?-- Python 并发 API 如何选择

如何选择正确的 Python 并发 API模块 ?
Python 标准库提供了三种并发 API , 如何知道你的项目应该使用哪个 API?

在本教程将带逐步了解各API的特性、区别以及各自应用场景,指导你选择最合适的并发 API。

多线程、多进程,还是异步?-- Python 并发 API 如何选择

Python 标准库提供了三种在程序中并发执行任务的方法。分别是 :

  • multiprocessing 模块用于基于进程的并发,
  • threading 模块用于基于线程的并发,
  • asyncio 模块用于基于协程的并发。

实际上还有1个选择,在 Python标准库里,基于 threading 与 multiprocessing 提供了1个异步执行多任务的高阶API: concurrent.futures 模块,由于其使用 Pool Executor 执行器API, 因此,本文后面称之为Executors API.

很多人为选择哪个感到头痛:

  • 例如,在选择了一个API之后,如果要并发执行多个任务,还要考虑应该使用工作池并发执行多任务,还是自己编写并发任务的代码?
  • 如果你选择使用工作池,还要考虑使用 multiprocessor Pools API 还是 Executors API?

即使是经验丰富的 Python 开发人员也对这些选项感到困惑。你应该为你的项目使用哪个 Python 并发 API? 你需要一些经验法则来指导选择最合适的并发 API。

选择哪个 Python 并发 API?

如需要在 Python 程序中使用并发。应该使用哪个 API? 这是最常见的问题之一。

这就是我写这本指南的原因。

首先,有三个主要的 Python 并发 API,它们是:

  • 基于协程,由 “asyncio” 模块提供。
  • 基于线程,由 “threading” 模块提供。
  • 基于进程,由 “multiprocessing” 模块提供。

在这三个之间选择相对直接。我将在下一节中向你展示如何做到这一点。

问题在于,还有更多的决定要做。你需要考虑是否应该使用工作池。例如:

  • 如果你决定需要基于线程的并发,你应该使用线程池还是以某种方式使用 Thread 类?
  • 如果你决定需要基于进程的并发,你应该使用进程池还是以某种方式使用 Process 类?

然后,如果你决定使用可重用的工作池进行并发,你有多个选项可供选择。

例如:

  • 如果你决定需要线程池,你应该使用 ThreadPool 类还是 ThreadPoolExecutor
  • 如果你决定需要进程池,你应该使用 Pool 类还是 ProcessPoolExecutor

下图总结了这些决策点。

在这里插入图片描述

你如何弄清楚这一切?如何选择适合你项目的 Python 并发 API?

选择 Python 并发 API 的过程

你可以使用的一种方法,也许是最常见的方法,是临时选择一个 API。许多开发决策都是这样做出的,你的程序可能会很好地工作。但也许不会。

我建议在选择适合你项目的 Python 并发 API 时,采用 3 步过程判断:

步骤如下:

  1. 第 1 步:任务是基于CPU 绑定 vs IO 绑定(多进程 vs 线程)
  2. 第 2 步:需要并发执行许多临时任务 vs 只是一个复杂任务?
  3. 第 3 步:用池(Pool) vs 执行器(executor)?

我还把决策分析树绘成了一个方便的图片,如下:

在这里插入图片描述

接下来,让我们仔细看看每个步骤并增加一些细微差别。

第 1 步:CPU 绑定任务 vs IO 绑定任务?

选择要使用的 Python 并发 API 的第一步是考虑你想要执行的任务或任务的限制因素。

任务主要是 CPU 绑定还是 IO 绑定?

CPU 绑定任务

CPU 绑定任务是一种涉及执行计算而不涉及 IO 的任务类型。这些操作只涉及内存中的数据,并对该数据执行计算。因此,这些操作的限制是 CPU 的速度。这就是为什么我们称它们为 CPU 绑定任务。

示例包括:

  • 估计圆周率。
  • 分解质数。
  • 解析 HTML、JSON 等文档。
  • 处理文本。
  • 运行模拟。

CPU 非常快,我们通常有一个以上的 多核CPU。我们希望执行我们的任务并充分利用现代硬件中的多个 CPU 核心。

现在我们已经熟悉了 CPU 绑定任务,让我们仔细看看 IO 绑定任务。

IO 绑定任务

IO 绑定任务是一种涉及从设备、文件或套接字连接中读取或写入的任务类型。这些操作涉及输入和输出 (IO),这些操作的速度受到设备、硬盘或网络连接的限制。这就是为什么这些任务被称为 IO 绑定的原因。

CPU 速度很快。现在CPU,主频可达 4GHz,每秒可以执行 40 亿条指令,你的系统中可能有一个以上的 CPU,每个CPU还有多个核。与 CPU 的速度相比,执行 I/O 操作非常慢。与设备交互、读写文件和套接字连接涉及调用操作系统中的指令(内核),它将等待操作完成。在等待期间,CPU 资源被闲置浪费了。

IO 绑定任务的示例包括:

  • 从硬盘读取或写入文件。
  • 读取或写入标准输出、输入或错误(stdin、stdout、stderr)。
  • 打印文档。
  • 下载或上传文件。
  • 查询服务器。
  • 查询数据库。
  • 拍照或录像。
  • 等等。

现在我们已经熟悉了 CPU 绑定和 IO 绑定任务,让我们考虑我们应该使用哪种类型的 Python 并发 API。

选择 Python 并发 API

回想一下,multiprocessing 模块提供基于进程的并发,threading 模块提供进程内的基于线程的并发。

通常,如果你有 CPU 绑定任务,你应该使用基于进程的并发。

如果你有 IO 绑定任务,你应该使用基于线程的并发。

  • CPU 绑定任务:使用 “multiprocessing” 模块进行基于进程的并发。
  • IO 绑定任务:使用 “threading” 模块进行基于线程的并发。

multiprocessing 模块适用于主要关注计算密集型的任务,这些任务之间共享的数据相对较少。因为进程之间共享的数据都必须序列化,会增加I/O时间,因此multiprocessing 不适用于任务之间发送或接收大量数据。

多进程可以让你可以利用每1个 CPU 核心或每个物理 CPU 核心来运行任务,并最大化利用底层硬件资源。

threading 模块适用于主要关注从 IO 设备读取或写入的任务,计算相对较少。由于全局解释器锁 (GIL) 阻止一次执行多个 Python 线程,threading 不适用于执行大量 CPU 计算的任务。GIL 通常只在执行阻塞操作时释放,比如 IO,或者在一些第三方 C 库中特别如此,比如 NumPy。

你可以执行数十、数百或数千个基于线程的任务,因为 IO 大部分时间都在等待。

下图总结了在线程模块的多线程并发和 multiprocessing 模块的进程并发之间进行选择的决策点。
在这里插入图片描述

可以参考本人关于 threading 和 multiprocessing 的文章:

  • Python 多进程编程指南
  • Python 多线程编程指南

接下来,让我们考虑 AsyncIO 是否合适。

在线程和 AsyncIO 之间进行选择

如果你的任务主要是 IO 绑定的,你有另一个决策点。你必须在 “threading” 模块和 “asyncio” 模块之间进行选择。回想一下,threading 模块提供基于线程的并发,asyncio 模块提供线程内的基于协程的并发。

通常,如果你有很多套接字连接(或者更喜欢异步编程),你应该使用基于协程的并发。否则,你应该使用基于线程的并发。

  • 多个套接字连接(包括 TCP/UDP, http, websocket等):使用 “asyncio” 模块进行基于协程的并发。
  • 否则:使用 “threading” 模块进行基于线程的并发。

asyncio 模块专注于套接字连接的并发非阻塞 IO。例如,如果你的 IO 任务是基于文件I/O, 数据库I/O的,那么 asyncio 可能不是最合适的选择,至少仅因为这一点。

原因是协程比线程更轻量级,因此一个线程可以托管比进程可以管理的线程多得多的协程。例如,asyncio 可能允许成千上万,甚至更多的协程用于基于套接字的 IO,而 threading API 可能只有几百到低数千个线程。

另一个考虑是你可能会想要或需要在开发程序时使用异步编程范式,例如 async/wait。因此,这个要求将覆盖任务所施加的任何要求。同样,你可能对异步编程范式有反感,因此这种偏好将覆盖任务所施加的任何要求。

第 2 步:并发执行许多临时任务 vs 一个复杂任务?

第二步是考虑你是否需要执行独立的临时任务或一个大型复杂任务。

我们在这一点上考虑的是,你是否需要发出一个或多个可能从可重用工作池中受益的临时任务。或者,你是否需要一个单一任务,其中可重用工作池将是多余的。

另一种思考方式是,你是否有一个或几个不同但复杂的任务,如监视器、调度程序或类似的任务,可能会持续很长时间,例如程序的持续时间。这些将不是临时任务,并且可能不会从可重用工作池中受益。

  • 短暂和/或许多临时:使用线程或进程池。
  • 长期和/或复杂任务:使用 ThreadProcess 类。

在你选择基于线程的并发的情况下,选择是在线程池或使用 Thread 类之间。

在你选择基于进程的并发的情况下,选择是在进程池或使用 Process 类之间。

一些额外的考虑包括:

  • 异构 vs 同质任务:池可能更适合一组不同的任务(异构),而 Process/Thread 类适合一种类型任务(同质)。
  • 重用 vs 一次性使用:池适合重用并发的基础,例如重用线程或进程执行多个任务,而 Process/Thread 类适合一次性任务,可能是一个长期任务。
  • 多个任务 vs 单个任务:池自然支持多个任务,可能以多种方式发出,而 Process/Thread 类只支持一种类型的任务,一次配置或覆盖。

让我们通过一些例子来具体化:

  • 一个 for 循环,每次迭代调用一个函数,每次使用不同的参数,可能适合线程池,因为可以自动重用工作线程完成每个任务。
  • 一个监视资源的后台任务可能适合 Thread/Process 类,因为它是一个长期运行的单一任务,可能有很多复杂和专业的功能,可能分散在许多函数调用中。
  • 一个下载许多文件的脚本可能适合工作池,因为每个任务持续时间很短,可能有更多的文件而不是工人,允许重用工人和排队任务以完成。
  • 一个维护内部状态并与主程序交互的一次性任务可能适合 Thread/Process 类,因为类可以被覆盖以使用实例变量进行状态管理,使用方法进行模块化功能。

下图可能有助于在工作池和 ThreadProcess 类之间进行选择。
在这里插入图片描述

第 3 步:池 vs 执行器?

第三步是考虑要使用的工作者池的类型。

它们有两种主要类型,它们是:

  • multiprocessing.pool.Pool 和支持线程的类的移植 multiprocessing.pool.ThreadPool
  • 执行器concurrent.futures.Executor 类和两个子类 ThreadPoolExecutorProcessPoolExecutor

两者都提供工作者池。相似之处众多,差异很少且微妙。

例如,相似之处包括:

  • 两者都有线程和基于进程的版本。
  • 两者都可以执行临时任务。
  • 两者都支持同步和异步任务执行。
  • 两者都提供检查状态和等待异步任务的支持。
  • 两者都支持异步任务的回调函数。

选择一个而不是另一个对你的程序不会有太大影响。主要区别在于每个提供的 API,特别是在任务处理的重点或方式上的微小差异。

例如:

  • 执行器提供取消已发布任务的能力,而池则没有。
  • 执行器提供处理不同类型任务集合的能力,而池则没有。
  • 执行器没有强制终止所有任务的能力,而池有。
  • 执行器没有提供多个并行版本的 map() 函数,而池有。
  • 执行器提供访问任务中引发的异常的能力,而池则没有。

我认为,关于池(Pool)真正重要是,池专注于使用许多不同版本的 map() 函数进行并发 for 循环,可将函数应用于一个可迭代参数中的每个参数。

执行器具有这种能力,但重点更多地是发布临时任务并异步管理任务集合。

下图有助于总结池和执行器之间的差异。
在这里插入图片描述

结束语

现在你知道如何在不同的 Python 并发 API 之间进行选择。

相关文章:

多线程、多进程,还是异步?-- Python 并发 API 如何选择

如何选择正确的 Python 并发 API模块 ? Python 标准库提供了三种并发 API , 如何知道你的项目应该使用哪个 API? 在本教程将带逐步了解各API的特性、区别以及各自应用场景,指导你选择最合适的并发 API。 多线程、多进程&#xff0…...

汽车服务管理系统 _od8kr

TOC springboot580汽车服务管理系统 _od8kr--论文 系统概述 该系统由个人管理员和员工管理,用户三部分组成。其中:用户进入系统首页可以实现首页,热销汽车,汽车配件,汽车资讯,后台管理,在线客…...

带你玩转小程序推广,实现短链接一键跳转

不知道各位有没有想过,短链接直接跳转到微信小程序到底该怎么操作呢?掌握这个小技能,能让你的推广效率大幅提升哦。今天就给大家分享一个全新方法,教你如何从短链接直接跳转到微信小程序,实现高效的一键式跨越。 一、…...

OpenDDS的Rtps_Udp传输协议可靠性QoS收发基本流程

OpenDDS中,实现了Rtps_Udp传输协议(非纯udp)的可靠性传输。传输的线程包括: 1)发送方线程主要线程和定时器 《1》应用线程 《2》网络异步发送线程 《3》Heartbeat定时器 《4》Nak_response定时器 2)接收方主要线程和定时器 《1》网络异步接收线程 《2》heartbeat_respons…...

体育数据API纳米奥运会数据API:高阶数据包接口文档API示例⑦

纳米体育数据的数据接口通过JSON拉流方式获取200多个国家的体育赛事实时数据或历史数据的编程接口,无请求次数限制,可按需购买,接口稳定高效;覆盖项目包括足球、篮球、网球、电子竞技、奥运等专题、数据内容。 纳米数据API2.0版本…...

【中项第三版】系统集成项目管理工程师 | 第 15 章 组织保障

前言 本章的知识点预计上午会考1-2分,下午可能会考,一般与其他管理领域进行结合考查。学习要以教材为主。 目录 15.1 信息和文档管理 15.1.1 信息和文档 15.1.2 信息(文档)管理规则和方法 15.2 配置管理 15.2.1 基本概念 …...

数据结构——顺序栈和链式栈

目录 引言 栈的定义 栈的分类 栈的功能 栈的声明 1.顺序栈 2.链式栈 栈的功能实现 1.栈的初始化 (1)顺序栈 (2)链式栈 (3)复杂度分析 2.判断栈是否为空 (1)顺序栈 (2)链式栈 (3)复杂度分析 3.返回栈顶元素 (1)顺序栈 (2)链式栈 (3)复杂度分析 4.返回栈的大…...

PHP轻创推客集淘客地推任务平台于一体的综合营销平台系统源码

🚀轻创推客,营销新纪元 —— 集淘客与地推任务于一体的全能平台🌐 🌈【开篇:营销新潮流,轻创推客引领未来】 在瞬息万变的营销世界里,你还在为寻找高效、全面的营销渠道而烦恼吗?&…...

three.js实现 加载3dtiles ,瓦片 ,倾斜摄影,功能

预览:https://z2586300277.github.io/three-cesium-examples/#/codeMirror?navigationThreeJS&classifyexpand&idloadTiles 部署站点预览:http://threehub.cn/ 开源地址:https://z2586300277.github.io/three-cesium-examples/#/e…...

Qt QTextEdit调用append数据重复的问题

使用QTextEdit写了个串口工具, 当串口有数据时通过一个signal传给slot,在 slot中调用QTextEdit的append(text)来增量显示串口数据,当串口关闭时调用clear()来清空显示。 结果发现append调用后显示的数据会有重复。 分析 分析代码&#xff0…...

数学基础(二)

一、导数 导数计算: 偏导数: 方向导数: 梯度: 函数在某点的梯度是一个向量,它的方向余方向导数最大值取得的方向一致。其大小正好是最大的方向导数 二、微积分 面积由来: 切线: 定积分&#x…...

Java设计模式原则及中介者模式研究

在软件开发过程中,设计模式作为解决常见设计问题的有效工具,对于提升代码质量、促进团队协作具有重要意义。本文系统地阐述了Java设计模式的六大基本原则——单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则以及迪米特法则&#…...

logstash入门学习

1、入门示例 1.1、安装 Redhat 平台 rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch cat > /etc/yum.repos.d/logstash.repo <<EOF [logstash-5.0] namelogstash repository for 5.0.x packages baseurlhttp://packages.elasticsearch.org…...

【代码】Swan-Transformer 代码详解(待完成)

1. 局部注意力 Window Attention (W-MSA Module) class WindowAttention(nn.Module):r""" Window based multi-head self attention (W-MSA) module with relative position bias.It supports both of shifted and non-shifted window.Args:dim (int): Number…...

iframe.contentDocument 和document.documentElement的区别

iframe.contentDocument 和 document.documentElement 是用于访问不同内容的两个不同的对象或属性。 1. iframe.contentDocument 内容: iframe.contentDocument 代表的是 <iframe> 元素所嵌入的文档的 Document 对象。它允许你访问和操作嵌入的文档&#xff08;即 ifram…...

计算机操作员试题(中篇)

计算机操作员试题(中篇) 335.在 Excel中,把鼠标指向被选中单元格边框,当指变成箭头时,拖动鼠标到目标单 元格时,将完成( )操作。 (A)删除 (B)移动 ©自动填充 (D)复制 336.在 Excel 工作表的单元格中,如想输入数字字符串 070615 (例如学号),则应输 入()。 (A) 0007…...

车规级MCU「换道」竞赛

汽车芯片&#xff0c;尤其是MCU市场正在进入拐点期。 本周&#xff0c;总部位于荷兰的汽车芯片制造商—恩智浦&#xff08;NXP&#xff09;半导体总裁兼首席执行官Kurt Sievers在公司第二季度财报电话会议上告诉投资者&#xff0c;由于汽车需求停滞不前&#xff0c;该公司正在努…...

数学生物学-2-离散时间模型(Discrete Time Models)

上一篇介绍了一个指数增长模型。然而&#xff0c;我们也看到&#xff0c;在现实情况下&#xff0c;细菌培养的增长是在离散的时间&#xff08;在这种情况下是小时&#xff09;进行测量的&#xff0c;种群并没有无限增长&#xff0c;而是趋于以S形曲线趋于平稳&#xff0c;称为“…...

免费开源!AI视频自动剪辑已成现实!效率提升80%,打工人福音!(附详细教程)

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 想象一下&#xff0c;假设老板给你布置了一项任务&#xff1a…...

NtripShare全站仪自动化监测之气象改正

最近有幸和自动化监测领域权威专家进行交流&#xff0c;讨论到全站仪气象改正的问题&#xff0c;因为有些观点与专家不太一致&#xff0c;所以再次温习了一下全站仪气象改正的技术细节。 气象改正的概念 全站仪一般利用光波进行测距&#xff0c;首先仪器会处理测距光波的相位漂…...

【人工智能】项目案例分析:使用自动编码器进行信用卡欺诈检测

一、项目背景 信用卡欺诈是金融行业面临的一个重要问题&#xff0c;快速且准确的欺诈检测对于保护消费者和金融机构的利益至关重要。本项目旨在通过利用自动编码器&#xff08;Autoencoder&#xff09;这一无监督学习算法&#xff0c;来检测信用卡交易中的欺诈行为&#xff0c…...

【工控】线扫相机小结

背景简介 我目前接触到的线扫相机有两种形式: 无采集卡,数据通过网线传输。 配备采集卡,使用PCIe接口。 第一种形式的数据通过网线传输,速度较慢,因此扫描和生成图像的速度都较慢,参数设置主要集中在相机本身。第二种形式的相机配备采集卡,通常速度更快,但由于相机和…...

将Web应用部署到Tomcat根目录的三种方法

将应用部署到Tomcat根目录的三种方法 将应用部署到Tomcat根目录的目的是可以通过"http://[ip]:[port]"直接访问应用&#xff0c;而不是使用"http://[ip]:[port]/[appName]"上下文路径进行访问。 方法一&#xff1a;&#xff08;最简单直接的方法&#xff0…...

工业和信息化部教育与考试中心计算机相关专业介绍

国家工信部的认证证书在行业内享有较高声誉。 此外&#xff0c;还设有专门的工业和信息化技术技能人才数据库查询服务&#xff0c;进一步方便了个人和企业对相关职业能力证书的查询需求。 序号 专业工种 级别 备注 1 JAVA程序员 初级 职业技术 2 电子…...

第二证券:生物天然气线上交易达成 创新探索互联互通、气证合一

8月20日&#xff0c;上海石油天然气生意中心在国内立异推出生物天然气线上生意。当日&#xff0c;绿气新动力&#xff08;北京&#xff09;有限公司&#xff08;简称“绿气新动力”&#xff09;挂单的1500万立方米生物天然气被百事食物&#xff08;我国&#xff09;有限公司&am…...

重磅!RISC-V+OpenHarmony平板电脑发布

仟江水商业电讯&#xff08;8月18日 北京 委托发布&#xff09;RISC-V作为历史上全球发展速度最快、创新最为活跃的开放指令架构&#xff0c;正在不断拓展高性能计算领域的边界。OpenHarmony是由开放原子开源基金会孵化并运营的开源项目&#xff0c;已成为发展速度最快的智能终…...

[DL]深度学习_扩散模型

扩散模型原理 深入浅出扩散模型 一、概念简介 1、Denoising Diffusion Probalistic Models&#xff0c;DDPM 1.1 扩散模型运行原理 首先sample一个都是噪声的图片向量&#xff0c;这个向量的shape和要生成的图像大小相同。通过Denoise过程来一步一步有规律的滤去噪声。Den…...

AI学习记录 - 如何快速构造一个简单的token词汇表

创作不易&#xff0c;有用的话点个赞 先直接贴代码&#xff0c;我们再慢慢分析&#xff0c;代码来自openai的图像分类模型的一小段 def bytes_to_unicode():"""Returns list of utf-8 byte and a corresponding list of unicode strings.The reversible bpe c…...

JAVA中的数组流ByteArrayOutputStream

Java 中的 ByteArrayOutputStream 是一个字节数组输出流&#xff0c;它允许应用程序以字节的形式写入数据到一个字节数组缓冲区中。以下是对 ByteArrayOutputStream 的详细介绍&#xff0c;包括其构造方法、方法、使用示例以及运行结果。 一、ByteArrayOutputStream 概述 Byt…...

S3C2440中断处理

一、中断处理机制概述 中断是CPU在执行程序过程中&#xff0c;遇到急需处理的事件时&#xff0c;暂时停止当前程序的执行&#xff0c;转而执行处理该事件的中断服务程序&#xff0c;并在处理完毕后返回原程序继续执行的过程。S3C2440提供了丰富的中断源&#xff0c;包括内部中…...

安卓系统上怎样做网站前端开发/营销的方法和技巧

//查看公钥openssl pkcs7 -inform DER -in android_.rsa -noout -print_certs -text//查看证书指纹keytool -printcert -file cert.rsa转载于:https://www.cnblogs.com/littlefishxu/p/3969198.html...

做网站封面要怎么做/今日广东头条新闻

DoNews 4月8日消息(记者 向密) 网易有道词典推出全新功能——AI作文批改&#xff0c;通过人工智能技术自动批改英语作文。据悉&#xff0c;有道AI作文批改基于网易有道神经网络机器翻译&#xff0c;使用Transformer架构等核心技术&#xff0c;建立复杂模型实现针对不同场景进行…...

做平面设计去哪些网站找图/浙江网络推广公司

nginx的ngx_pagespeed是一个前段加速模块 安装需要的nginx依赖环境 yum install gcc gcc-c pcre* zlib-devel openssl-devel gd-devel php php-mysql php-fpm geoip-devel -y 下载nginx的12版本 不建议用高版本的会有bug wget http://nginx.org/download/nginx-1.12.2.tar.gz …...

北京公司模板网站好/石家庄网络关键词排名

前面的直接插入算法&#xff0c;以抓牌为例&#xff0c;假设手气比较好&#xff0c;抓牌的顺序是从A直到K&#xff0c;那么在这整个过程中都无需进行任何的插入动作&#xff0c;只需要将每次抓到的牌放在最后即可。假设现在手上已经抓到的牌的顺序中逆序比较少&#xff0c;则抓…...

网站如何备案 流程/手机app免费制作平台

两种方式 方式一&#xff1a;前端处理 用escape()处理可能含有特殊字符的参数 方式二&#xff1a;后端处理 用 HtmlUtils.htmlUnescape() 处理可能含有特殊字符的参数 需引入依赖&#xff1a; <dependency><groupId>org.springframework</groupId><…...

南昌哪个公司做网站好/免费的网站软件

很抱歉&#xff0c;作为一个 AI 语言模型&#xff0c;我不能为您完整写一篇本科毕业论文并以 .dwcx 格式输出。但是&#xff0c;我可以给您提供一些相关的资料&#xff0c;并且帮助您更好地理解和研究这个课题。如果您需要具体的建议和帮助&#xff0c;请告诉我您具体需要解决的…...