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

MFC线程安全案例

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

目录

一、项目解析

二、多线程安全机制

2.1、互斥锁

2.2、临界区

三、项目实现

3.1、在Dialg资源UI控制好界界面

3.2、通过类封装 线程操作

3.3、通过消息映射机制处理点击事件

3.4、项目演示 


一、项目解析

这里我们就要控制线程对临界资源的访问,通过UI界面进行测试。 

二、多线程安全机制

在多线程中,当多个线程访问同一份临界资源的时候,可能会出现线程之间竞争的操作,比如有二个线程A和B,他们都要进行抢票操作, int ticket=5000,线程每次抢到一份票就进行ticket--操作。

每个线程中都有下面的判断

while (1)
{if (ticket <= 0){std::cout << "停止抢票"<<std::endl;break;}else{--ticket;}
}

当没有票的时候,就停止抢票。但是我们发现ticket会出现负数的情况。按理说是这是不可能的,为什么呢?这是因为--操作是非原子的。

原子操作(Atomic Operation)

原子操作是指一组操作在执行过程中不可被中断的操作。无论在多线程或并发环境中,原子操作都是一个不可分割的单位,即使在操作执行过程中,其他线程也无法干扰该操作。这保证了操作的完整性和一致性。

非原子操作(Non-Atomic Operation)

非原子操作是指在执行过程中可能会被其他线程或进程中断的操作。在多线程或并发环境下,非原子操作可能会导致数据竞争、冲突或不一致的结果。

那为什么会出现负数呢?

 首先,我们来分析一下非原子操作:

--ticket;  // 非原子操作

这个操作实际上包含了三个步骤: 

  • 读取 ticket 的当前值。
  • 对该值进行减 1 操作。
  • 将新的值写回 ticket

在多线程环境中,当多个线程(比如线程A和线程B)同时执行这段代码时,可能会出现以下情况:

场景示例:

假设 ticket 的值为 1,线程 A 和线程 B 同时开始抢票。

  • 线程A 读取了 ticket 的值为1,刚刚执行完--操作,但是还没有来的急,将信息写会回内存变量,OS(操作系统就调度线程B进行抢票)
  • 线程B此时发现ticket 的值为1,可以进行抢票,线程B也执行了--操作。
  • 这个时候线程A的时间片到了,继续执行后面的操作把结果写会变量为0
  • 这个线程B继续往后执行,也讲--的结果写会,最终ticketw为服数

这样,ticket 同一份资源被用了二个线程同时使用,造成了资源不一致,当多个线程都在进行类似的操作时,ticket 的值有可能被多次修改,最终可能导致 ticket 为负数。

为了解决在多线程中,那些线程不安全的问题,我们要使用下面的安全操作

2.1、互斥锁

互斥锁(Mutex,全称 Mutual Exclusion Lock)是一种用于 多线程同步 的机制,主要用于保护共享资源,确保同一时刻只有一个线程能够访问共享数据,从而避免数据竞争和不一致性。互斥锁通过加锁和解锁操作来控制对临界区的访问。

C++11 引入了 std::mutex 类来提供互斥锁的功能。它位于 <mutex> 头文件中。

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;  // 创建一个互斥锁void printHello() {mtx.lock();  // 加锁std::cout << "Hello from thread!" << std::endl;mtx.unlock();  // 解锁
}int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

 使用 std::lock_guard 自动管理锁

std::lock_guard 是一个 RAII(Resource Acquisition Is Initialization)类,它用于自动加锁和解锁。std::lock_guard 在创建时会加锁,在销毁时会自动解锁。这种方式更安全,避免了手动解锁时可能出现的错误(例如忘记解锁

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void printHello() {std::lock_guard<std::mutex> lock(mtx);  // 自动加锁std::cout << "Hello from thread!" << std::endl;
}  // 自动解锁int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

std::unique_lock:更灵活的锁管理 

std::unique_lock 是另一种锁管理类,它提供了更多的功能,如可以手动解锁、延迟加锁、以及可以在多个锁上进行组合。

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void printHello() {std::unique_lock<std::mutex> lock(mtx);  // 加锁std::cout << "Hello from thread!" << std::endl;// lock 会在作用域结束时自动解锁
}int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

锁的死锁(Deadlock) 

死锁是指两个或多个线程因相互等待而无法继续执行的情况。例如,线程 A 持有锁 1,等待锁 2;线程 B 持有锁 2,等待锁 1。这样,两个线程相互等待对方释放锁,导致程序进入死锁状态。

2.2、临界区

临界区是指一段在多线程程序中访问共享资源的代码区域,多个线程必须互斥地访问这个区域,以避免出现数据竞争和资源冲突。简单来说,临界区是多个线程共享的数据被操作的区域,这些数据需要保护起来,以确保在任何时刻只有一个线程能够访问和修改这些共享数据。

Windows 操作系统中,创建和使用临界区的 API 提供了多线程同步的机制。Windows 提供了 CRITICAL_SECTION 类型,它是操作系统用于线程同步的基本工具。CRITICAL_SECTION 用于保护共享资源,确保同一时刻只有一个线程能够进入临界区。

1. 初始化临界区

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

lpCriticalSection:指向一个 CRITICAL_SECTION 结构的指针,系统会根据这个结构初始化临界区。 

2. 删除临界区

当不再需要临界区时,应该调用 DeleteCriticalSection 来销毁它。这样可以释放与临界区相关的资源。

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

3. 加锁临界区 

使用 EnterCriticalSection 函数来加锁临界区。当一个线程试图访问临界区时,它必须首先请求锁。如果锁已经被其他线程持有,则该线程将会被阻塞,直到锁变得可用。 

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

 4. 解锁临界区

使用 LeaveCriticalSection 来解锁临界区,这样其他线程可以访问临界区中的资源。

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

要使用临界资源,我们要先定义应该临界区,比如: CRITICAL_SECTION m_critical_section对象

,然后调用相应的函数就好了。如EnterCriticalSection(&m_CriticalSection);

三、项目实现

为了实现这个项目,我们通过MFC创建应该基于对话框的项目。

3.1、在Dialg资源UI控制好界界面

这里要注意为对话框添加变量名,因为我要显示线程运行的数据在上面。

这里我们在对话框的.h头文件中可以看到 

 

3.2、通过类封装 线程操作

 ThreadMagager.h在这个头文件中实现

#pragma once#include<functional>
#include<thread>
#include<atomic>
#include <windows.h> 
#include <memory> 
namespace pjb
{class ThreadManager{public:ThreadManager();~ThreadManager();void StartThread(int step, unsigned int interval, std::function<void(int)> callback);void StopThread();bool IsRunning()const { return m_Running; };private:std::thread m_Thread;//std::unique_ptr<std::thread> m_Thread;std::atomic<bool> m_Running;//创建原子对象,保证线程安全int m_iCount;//计数器int m_iStep;//步长unsigned int m_uiInterval;//时间间隔std::function<void(int)> m_UpdateCallback;//更新回调函数CRITICAL_SECTION m_CriticalSection; // 临界区,用于线程同步void _ThreadProc();};
}

在 ThreadMagager.cpp中实现细节操作


#include "pch.h"//这个头我文件记得包含
#include"ThreadMagager.h"
#include<chrono>// 构造函数:初始化成员变量和临界区
pjb::ThreadManager::ThreadManager(): m_Running(false), m_iCount(0), m_iStep(0), m_uiInterval(0)
{InitializeCriticalSection(&m_CriticalSection); // 初始化临界区
}// 析构函数:停止线程并销毁临界区
pjb::ThreadManager::~ThreadManager()
{StopThread();DeleteCriticalSection(&m_CriticalSection); // 销毁临界区
}
void pjb::ThreadManager::StartThread(int step, unsigned int interval, std::function<void(int)> callback)
{//判断线程是否已经启动if (m_Running) return;//设置线程的参数m_iStep = step;m_uiInterval = interval;m_UpdateCallback = callback;m_Running = true;//启动线程// 使用智能指针来管理资源,防止内存泄漏//m_Thread = std::make_unique<std::thread>(&ThreadManager::ThreadProc, this);//std::thread t(function, args...);的参数//function为可调用对象,将会做为线程的主体函数。传this指针,是为了告诉线程在哪个 ThreadManager 对象上调用这个成员函数m_Thread = std::thread(&pjb::ThreadManager::_ThreadProc, this);}
void pjb::ThreadManager::StopThread()
{if (!m_Running)return;m_Running = false;//判断线程是否能被join()。if (m_Thread.joinable()){//m_Thread.join();//等待线程完成,这种方式会阻塞UI线程,导致用户无法操作m_Thread.detach();  // 分离线程,让它在后台执行,UI线程可以继续操作,当程序结束后,操作系统会回收资源}
}
//线程处理
void pjb::ThreadManager::_ThreadProc()
{while (m_Running){// 使用临界区保护共享资源EnterCriticalSection(&m_CriticalSection);// 检查计数器是否已经达到上限if (m_iCount >= 10000){m_Running = false; // 停止线程LeaveCriticalSection(&m_CriticalSection);  // 离开临界区break; // 退出循环}//更新计数器m_iCount += m_iStep;LeaveCriticalSection(&m_CriticalSection); // 离开临界区//调用回调函数,更新计数值if (m_UpdateCallback){m_UpdateCallback(m_iCount);}//休眠指定时间,然后继续进行更新std::this_thread::sleep_for(std::chrono::milliseconds(m_uiInterval));//毫秒}
}

这里我们在 线程处理函数_ThreadProc()中创建临界区,限制了访问临界资源时候只运行一个线程访问。

3.3、通过消息映射机制处理点击事件

对于对话框的头文件没有什么好说的,大家看一下就可以了。

// ThreadOperateDlg.h: 头文件
//#pragma once
#include"ThreadMagager.h"// CThreadOperateDlg 对话框
class CThreadOperateDlg : public CDialogEx
{
// 构造
public:CThreadOperateDlg(CWnd* pParent = nullptr);	// 标准构造函数// 对话框数据
#ifdef AFX_DESIGN_TIMEenum { IDD = IDD_THREADOPERATE_DIALOG };
#endifprotected:virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持// 实现
protected:HICON m_hIcon;// 生成的消息映射函数virtual BOOL OnInitDialog();afx_msg void OnSysCommand(UINT nID, LPARAM lParam);afx_msg void OnPaint();afx_msg HCURSOR OnQueryDragIcon();DECLARE_MESSAGE_MAP()
private:pjb::ThreadManager m_Thread1;pjb::ThreadManager m_Thread2;CEdit m_show1;CEdit m_show2;
public:afx_msg void OnBnClickedButton1();afx_msg void OnBnClickedButton2();

在对话框.cpp文件中处理点击事情,就是通过前面我们封装好的接口去处理即可

//线程启动
void CThreadOperateDlg::OnBnClickedButton1()
{// TODO: 在此添加控件通知处理程序代码//启动线程//情况2使用共享计数变量:m_Thread1.StartThread(1, 10, [this](int value) {CString str;//格式转换,将整形转换为字符串str.Format(_T("%d"), value);GetDlgItem(IDC_EDIT1)->SetWindowTextW(str);if (_ttoi(str) >= 10000){//将启动线程的按键禁用GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);}});//线程2m_Thread2.StartThread(2, 20, [this](int value) {CString str;//格式转换,将整形转换为字符串str.Format(_T("%d"), value);GetDlgItem(IDC_EDIT2)->SetWindowTextW(str);if (_ttoi(str) >= 10000){//将启动线程的按键禁用GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);}});}void CThreadOperateDlg::OnBnClickedButton2()
{// TODO: 在此添加控件通知处理程序代码m_Thread1.StopThread();m_Thread2.StopThread();
}

3.4、项目演示 

这里我们点击启动线程,就可以观察到对话框的变化。

 

相关文章:

MFC线程安全案例

作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、项目解析 二…...

【Elasticsearch】bucket_sort

Elasticsearch 的bucket_sort聚合是一种管道聚合&#xff0c;用于对父多桶聚合&#xff08;如terms、date_histogram、histogram等&#xff09;的桶进行排序。以下是关于bucket_sort的详细说明&#xff1a; 1.基本功能 bucket_sort聚合可以对父聚合返回的桶进行排序&#xff…...

计算机毕业设计——Springboot点餐平台网站

&#x1f4d8; 博主小档案&#xff1a; 花花&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 花花在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于java、python等技术。近年来&#xff0c;花花更…...

MATLAB中count函数用法

目录 语法 说明 示例 对出现次数计数 使用模式对数字和字母进行计数 多个子字符串的所有出现次数 忽略大小写 对字符向量中的子字符串进行计数 count函数的功能是计算字符串中模式的出现次数。 语法 A count(str,pat) A count(str,pat,IgnoreCase,true) 说明 A c…...

Win11下搭建Kafka环境

目录 一、环境准备 二、安装JDK 1、下载JDK 2、配置环境变量 3、验证 三、安装zookeeper 1、下载Zookeeper安装包 2、配置环境变量 3、修改配置文件zoo.cfg 4、启动Zookeeper服务 4.1 启动Zookeeper客户端验证 4.2 启动客户端 四、安装Kafka 1、下载Kafka安装包…...

51c自动驾驶~合集49

我自己的原文哦~ https://blog.51cto.com/whaosoft/13164876 #Ultra-AV 轨迹预测新基准&#xff01;清华开源&#xff1a;统一自动驾驶纵向轨迹数据集 自动驾驶车辆在交通运输领域展现出巨大潜力&#xff0c;而理解其纵向驾驶行为是实现安全高效自动驾驶的关键。现有的开…...

nexus部署及配置https访问

1. 使用docker-compose部署nexus docker-compose-nexus.yml version: "3" services:nexus:container_name: my-nexusimage: sonatype/nexus3:3.67.1hostname: my-nexusnetwork_mode: hostports:- 8081:8081deploy:resources:limits:cpus: 4memory: 8192Mreservations…...

ffmpeg -hwaccels

1. ffmpeg -hwaccels -loglevel quiet 显示ffmpeg支持的硬件设备 2. 输出 Hardware acceleration methods: vdpau cuda vaapi qsv drm opencl 3. 说明 输出中的cuda表示ffmpeg支持Nvidia 硬件设备。编译ffmpeg增加相关硬件设备的配置&#xff0c;输出会显示相应的信…...

Python——批量图片转PDF(GUI版本)

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…...

LabVIEW无人机飞行状态监测系统

近年来&#xff0c;无人机在农业植保、电力巡检、应急救灾等多个领域得到了广泛应用。然而&#xff0c;传统的目视操控方式仍然存在以下三大问题&#xff1a; 飞行姿态的感知主要依赖操作者的经验&#xff1b; 飞行中突发的姿态异常难以及时发现&#xff1b; 飞行数据缺乏系统…...

算法16(力扣451)——根据字符出现频率排序

1、问题 给定一个字符串 s &#xff0c;根据字符出现的 频率 对其进行 降序排序 。一个字符出现的频率 是它出现在字符串中的次数&#xff0c; 返回 已排序的字符串。如果有多个答案&#xff0c;返回其中任何一个。 2、示例 &#xff08;1&#xff09; 输入: s "tree&q…...

Response 和 Request 介绍

怀旧网个人博客网站地址&#xff1a;怀旧网&#xff0c;博客详情&#xff1a;Response 和 Request 介绍 1、HttpServletResponse 1、简单分类 2、文件下载 通过Response下载文件数据 放一个文件到resources目录 编写下载文件Servlet文件 public class FileDownServlet exten…...

ADB详细教程

目录 一、ADB简介 二、配置 配置环境变量 验证是否安装成功 三、简单使用 基本命令 设备连接管理 USB连接 WIFI连接&#xff08;需要USB线&#xff09; 开启手机USB调试模式 开启USB调试 四、其他 更换ADB默认启动端口 一、ADB简介 ADB&#xff08;Android Debug…...

Jenkins+gitee 搭建自动化部署

Jenkinsgitee 搭建自动化部署 环境说明&#xff1a; 软件版本备注CentOS8.5.2111JDK1.8.0_211Maven3.8.8git2.27.0Jenkins2.319最好选稳定版本&#xff0c;不然安装插件有点麻烦 一、安装Jenkins程序 1、到官网下载相应的版本war或者直接使用yum安装 Jenkins官网下载 直接…...

今日AI和商界事件(2025-02-11)

今日AI大事件主要包括以下几个方面&#xff1a; 一、行业竞购与合作变动 马斯克组团竞购OpenAI 据《华尔街日报》报道&#xff0c;马斯克率投资者财团出价974亿美元竞购OpenAI&#xff0c;欲使其回归开源公益使命。xAI支持此次竞购&#xff0c;若成功&#xff0c;xAI或与OpenA…...

oracle dbms_sqltune 使用

创建测试表 CREATE TABLE test_table (id NUMBER PRIMARY KEY,event_date DATE,value NUMBER );插入测试数据 DECLAREi NUMBER; BEGINFOR i IN 1..1000000 LOOPINSERT INTO test_table (id, event_date, value)VALUES (i, SYSDATE - MOD(i, 365), DBMS_RANDOM.VALUE(1, 1000)…...

大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡

大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡 背景 前端开发接口请求&#xff0c;调试&#xff0c;联调&#xff0c;接入数据&#xff0c;前端必不可少工具&#xff0c;postman是一个非常好…...

AI大语言模型

一、AIGC和生成式AI的概念 1-1、AIGC Al Generated Content&#xff1a;AI生成内容 1-2、生成式AI&#xff1a;generative ai AIGC是生成式 AI 技术在内容创作领域的具体应用成果。 目前有许多知名的生成式 AI&#xff1a; 文本生成领域 OpenAI GPT 系列百度文心一言阿里通…...

数智百问 | 制造企业如何降低产线检测数据的存储和管理成本?

在《“十四五”智能制造发展规划》等政策的推动下&#xff0c;以及新能源汽车、消费电子等品牌商对产品质量和供应商智能化水平要求的提升&#xff0c;半导体、电子制造、动力电池等先进制造行业企业纷纷推进产线智能化升级&#xff0c;并投入大量机器视觉检测设备以实现自动化…...

Pdf手册阅读(1)--数字签名篇

原文阅读摘要 PDF支持的数字签名&#xff0c; 不仅仅是公私钥签名&#xff0c;还可以是指纹、手写、虹膜等生物识别签名。PDF签名的计算方式&#xff0c;可以基于字节范围进行计算&#xff0c;也可以基于Pdf 对象&#xff08;pdf object&#xff09;进行计算。 PDF文件可能包…...

Python 识别图片和扫描PDF中的文字

目录 工具与设置 Python 识别图片中的文字 Python 识别图片中的文字及其坐标位置 Python 识别扫描PDF中的文字 注意事项 在处理扫描的PDF和图片时&#xff0c;文字信息往往无法直接编辑、搜索或复制&#xff0c;这给信息提取和分析带来了诸多不便。手动录入信息不仅耗时费…...

C++引用深度详解

C引用深度详解 前言1. 引用的本质与核心特性1.1 引用概念1.2 核心特性 2. 常引用与权限控制2.1 权限传递规则2.2 常量引用2.3 临时变量保护1. 样例2. 样例3. 测试 三、引用使用场景分析3.1 函数参数传递输出型参数避免多级指针高效传参 3.2 做函数返回值正确使用危险案例 4. 性…...

SpringCloud - Gateway 网关

前言 该博客为Sentinel学习笔记&#xff0c;主要目的是为了帮助后期快速复习使用 学习视频&#xff1a;7小快速通关SpringCloud 辅助文档&#xff1a;SpringCloud快速通关 源码地址&#xff1a;cloud-demo 一、简介 官网&#xff1a;https://spring.io/projects/spring-clou…...

【JVM详解五】JVM性能调优

示例&#xff1a; 配置JVM参数运行 #前台运行 java -XX:MetaspaceSize-128m -XX:MaxMetaspaceSize-128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio8 - XX:UseConcMarkSweepGC -jar /jar包路径 #后台运行 nohup java -XX:MetaspaceSize-128m -XX:MaxMetaspaceS…...

基于 STM32 平台的音频特征提取与歌曲风格智能识别系统

标题:基于 STM32 平台的音频特征提取与歌曲风格智能识别系统 内容:1.摘要 摘要&#xff1a;本文介绍了一种基于 STM32 平台的音频特征提取与歌曲风格智能识别系统。该系统通过对音频信号进行特征提取和分析&#xff0c;实现了对歌曲风格的自动识别。在特征提取方面&#xff0c…...

DeepSeek:搅动人工智能产业风云的鲶鱼效应深度解读

我的个人主页 我的专栏&#xff1a;人工智能领域&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 引言 在当今科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;无疑是最为耀眼的领域之一。众多技术与平台如繁星般涌现&a…...

一觉醒来全球编码能力下降100000倍,新手小白的我决定科普C语言——三子棋游戏实现

硬控我一上午&#xff0c;小编还是太菜了&#xff0c;大家可以自行升级电脑难度&#xff0c;也可以升级游戏到五子棋 1.game.h #pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3//初始化棋盘 void InitBoa…...

学习 URL 传参中哪些字符是支持的,哪些是不支持的

URL 的结构 URL 由多个部分组成&#xff0c;包括协议、域名、路径、查询参数和片段标识符&#xff0c;其中&#xff0c;查询参数部分就是问号后面的内容&#xff0c;这部分使用键值对&#xff0c;中间用 & 分隔。比如&#xff0c;http://example.com/path?key1value1&…...

bingAI生成的易语言编程基础

易语言编程基础 易语言&#xff08;EPL&#xff09;是一种基于中文的编程语言&#xff0c;旨在简化编程学习过程&#xff0c;特别适合初学者和有一定编程基础的开发者。它通过中文关键词和语法&#xff0c;降低了编程的门槛&#xff0c;使得代码更加直观易懂。 示例&#xff…...

HTML应用指南:利用POST请求获取接入比亚迪业态的充电桩位置信息

在新能源汽车快速发展的今天,充电桩的分布和可用性成为了影响用户体验的关键因素之一。比亚迪作为全球领先的新能源汽车制造商,不仅在车辆制造方面取得了卓越成就,也在充电基础设施建设上投入了大量资源。为了帮助用户更方便地找到比亚迪充电桩的位置,本篇文章,我们将探究…...