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

【Linux】从多线程同步到生产者消费者模型:多线程编程实践

目录

1.线程的同步

1.1.为什么需要线程的同步?

2.2.条件变量的接口函数

2.生产消费模型

2.1 什么是生产消费模型

2.2.生产者消费者模型优点

2.3.为何要使用生产者消费者模型

3.基于BlockingQueue的生产者消费者模型

3.1为什么要将if判断变成while?

3.2.pthread_cond_wait函数调用的作用:

代码:

4.POSIX信号量

4.1.POISX信号量是什么?

4.2.POISX信号量常见接口

4.3.POSIX信号量的核心PV操作

4.3.1、P操作(等待信号量)

4.3.2、V操作(释放信号量)

5.环形队列

5.1.生产消费模型搭建的原理

5.2.环形队列的具体实现

5.3.代码:

5.4.多生产和多消费的并发性体现在:

1.线程的同步

1.1.为什么需要线程的同步?

上面我们讲解了线程的互斥问题,但此时我们又发现了一个问题!

如果某一个线程抢票能力过于强大,把所有的票一个人都抢走了,比如上面的线程4,一个人就抢到了8088张票,而线程2和线程3一张票都没有抢到,这就造成了线程2和线程3的饥饿问题!

在现实世界里,这肯定是不行的,秉持着公平公正的原则,我们应该让这4个线程抢到的票都差不多,才有实际意义。

所以互斥能解决抢票抢到负数的问题,但是不能解决饥饿问题,饥饿问题就需要线程同步去解决!

通过条件变量我们可以实现线程的同步!

2.2.条件变量的接口函数

int pthread_cond_init(pthread_cond_t *restrict cond , const pthread_condattr_t *restrictattr);:初始化接口
int pthread_cond_destroy(pthread_cond_t *cond):销毁接口
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);:在条件不满足时阻塞等待
int pthread_cond_broadcast(pthread_cond_t *cond);:条件满足,唤醒所有线程,开始竞争。
int pthread_cond_signal(pthread_cond_t *cond);:条件满足,唤醒一个线程。

条件变量需要一个线程队列和相应的通知机制,才能保证线程同步!

2.生产消费模型

2.1 什么是生产消费模型

总结一句话就是“321”原则:

  1. 一个交易场所(特定数据结构形式存在的一段内存空间)
  2. 两种角色(生产角色,消费角色):生产线程,消费线程
  3. 三种关系:生产与生产(互斥关系) , 消费与消费(互斥关系),生产与消费。

1个交易场指的就是共享资源(临界资源),有多个厂商(生产者)和多个用户(消费者),所以这就是我们常说的多线程的同步和互斥问题。

超市是什么?临时保存数据的“内存空间”——某种数据结构对象。

商品是什么?就是数据!

2.2.生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

2.3.为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的

3.基于BlockingQueue的生产者消费者模型

 

3.1为什么要将if判断变成while?

如果生产者只生产了一份,但是叫醒了5个消费者,当一个消费者竞争锁结束取走仅有的一份商品,那接下来的4个消费者就会看到空的队列,如果是if,因为之前已经判断过,所以会直接执行下面取空的队列,因此会直接报错,但是如果是while的话,仍需要判断队列是否已经满了,因为当等待的线程被唤醒的时候,继续从当前的位置进行执行代码!

3.2.pthread_cond_wait函数调用的作用:

a. 让调用线程等待

b. 自动释放曾经持有的_mutex锁

c. 当条件满足,线程唤醒,pthread_cond_wait要求线程必须重新竞争_mutex锁,竞争成功,方可返回!!!

代码:

#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>template <typename T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _cap;}bool IsEmpty(){return _block_queue.empty();}
public:BlockQueue(int cap) : _cap(cap){_productor_wait_num = 0;_consumer_wait_num = 0;pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_product_cond, nullptr);pthread_cond_init(&_consum_cond, nullptr);}void Enqueue(T &in) // 生产者用的接口{pthread_mutex_lock(&_mutex);while(IsFull()) // 保证代码的健壮性{// 生产线程去等待,是在临界区中休眠的!你现在还持有锁呢!!!// 1. pthread_cond_wait调用是: a. 让调用线程等待 b. 自动释放曾经持有的_mutex锁 c. 当条件满足,线程唤醒,pthread_cond_wait要求线性// 必须重新竞争_mutex锁,竞争成功,方可返回!!!// 之前:安全_productor_wait_num++;pthread_cond_wait(&_product_cond, &_mutex);  //  只要等待,必定会有唤醒,唤醒的时候,就要继续从这个位置向下运行!!_productor_wait_num--;// 之后:安全}// 进行生产// _block_queue.push(std::move(in));// std::cout << in << std::endl;_block_queue.push(in);// 通知消费者来消费if(_consumer_wait_num > 0)pthread_cond_signal(&_consum_cond); // pthread_cond_broadcastpthread_mutex_unlock(&_mutex);}void Pop(T *out) // 消费者用的接口 --- 5个消费者{pthread_mutex_lock(&_mutex);while(IsEmpty()) // 保证代码的健壮性{// 消费线程去等待,是在临界区中休眠的!你现在还持有锁呢!!!// 1. pthread_cond_wait调用是: a. 让调用进程等待 b. 自动释放曾经持有的_mutex锁_consumer_wait_num++;pthread_cond_wait(&_consum_cond, &_mutex);  // 伪唤醒_consumer_wait_num--;}// 进行消费*out = _block_queue.front();_block_queue.pop();// 通知生产者来生产if(_productor_wait_num > 0)pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);// pthread_cond_signal(&_product_cond);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}private:std::queue<T> _block_queue;   // 阻塞队列,是被整体使用的!!!int _cap;                     // 总上限pthread_mutex_t _mutex;       // 保护_block_queue的锁pthread_cond_t _product_cond; // 专门给生产者提供的条件变量pthread_cond_t _consum_cond;  // 专门给消费者提供的条件变量int _productor_wait_num;int _consumer_wait_num;
};#endif

我们之前学习了基于条件变量和阻塞队列实现(空间可以动态分配)的生产消费者模型,今天我们来用POSIX信号量基于固定大小的环形队列重写这个程序。

4.POSIX信号量

4.1.POISX信号量是什么?

信号量本质是一个计数器,可以在初始化时对设置资源数量,进程 / 线程 可以获取信号量来对资源进行操作和结束操作可以释放信号量!

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

4.2.POISX信号量常见接口

信号量初始化:

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数分别为:
sem_t *sem:传入信号量的地址
pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
value:信号量的初始值(计数器的初始值)

信号量销毁:

#include <semaphore.h>
int sem_destroy(sem_t *sem)

4.3.POSIX信号量的核心PV操作

POSIX信号量的PV操作是信号量机制中的核心,它们分别代表了对信号量的等待(P操作)和释放(V操作)。

4.3.1、P操作(等待信号量)

P操作,也称为“申请资源”或“等待信号量”操作,用于尝试减少信号量的值。当线程或进程需要访问某个临界资源时,它会执行P操作来申请信号量。

  1. 函数原型

    int sem_wait(sem_t *sem);

    其中,sem是指向要等待的信号量的指针。

  2. 操作过程

    • 如果信号量的当前值大于0,那么P操作会将信号量的值减1,并立即返回,表示申请资源成功。
    • 如果信号量的当前值为0,那么执行P操作的线程或进程将被阻塞,直到信号量的值变为大于0(即有其他线程或进程释放了信号量)。此时,被阻塞的线程或进程会重新尝试P操作,如果成功,则信号量的值再次减1。
  3. 返回值

    • 成功时,返回0。
    • 失败时,返回-1,并设置errno来指示错误类型。

4.3.2、V操作(释放信号量)

V操作,也称为“释放资源”或“发布信号量”操作,用于增加信号量的值。当线程或进程完成对临界资源的访问后,它会执行V操作来释放信号量。

  1. 函数原型

    int sem_post(sem_t *sem);

    其中,sem是指向要释放的信号量的指针。

  2. 操作过程

    • V操作会将信号量的值加1
    • 如果有线程或进程因为信号量的值为0而被阻塞在P操作上,那么V操作会唤醒其中一个被阻塞的线程或进程,使其能够继续执行P操作并访问临界资源。
  3. 返回值

    • 总是返回0,表示成功。V操作永远不会阻塞。

注意PV操作是原子的,这意味着它们在执行过程中不会被其他线程或进程的打断。这保证了信号量机制的正确性和可靠性。

5.环形队列

5.1.生产消费模型搭建的原理

环形队列底层也是普通数组,

生产者和消费者指向同一位置有两种情况:

  1. 队列为空(让生产者先跑)
  2. 队列为满(让消费者先跑)

环形队列当队列不为空或者满的时候,真正实现了多线程同步。当然生产者不能把消费者套一个圈,消费者不能超过生产者。这些都可以通过POSIX信号量的特性实现~

5.2.环形队列的具体实现

首先需要区分生产者和消费者,生产者只关注空间,消费者只关注资源。生产者和消费者都需要进行PV操作,生产者对应的将任务加入队列,消费者对应的取出队列里的任务。

  1. Consumer线程不断从环形队列中取出Task对象,执行其操作,并打印消费结果。
  2. Productor线程则持续生成新的Task对象并将其放入队列中,同时打印出生产信息。

并且还需要两把锁,分别给生产者和消费者,保证多线程并发的线程安全。

5.3.代码:

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <pthread.h>template<typename T>
class RingQueue
{
private:void P(sem_t &sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}
public:RingQueue(int cap): _ring_queue(cap), _cap(cap),  _productor_step(0), _consumer_step(0){sem_init(&_room_sem, 0, _cap);sem_init(&_data_sem, 0, 0);pthread_mutex_init(&_productor_mutex, nullptr);pthread_mutex_init(&_consumer_mutex, nullptr);}void Enqueue(const T &in){// 生产行为P(_room_sem);Lock(_productor_mutex);// 一定有空间!!!_ring_queue[_productor_step++] = in; // 生产_productor_step %= _cap;Unlock(_productor_mutex);V(_data_sem);}void Pop(T *out){// 消费行为P(_data_sem);Lock(_consumer_mutex);*out = _ring_queue[_consumer_step++];_consumer_step %= _cap;Unlock(_consumer_mutex);V(_room_sem);}~RingQueue(){sem_destroy(&_room_sem);sem_destroy(&_data_sem);pthread_mutex_destroy(&_productor_mutex);pthread_mutex_destroy(&_consumer_mutex);}
private:// 1. 环形队列std::vector<T> _ring_queue;int _cap; // 环形队列的容量上限// 2. 生产和消费的下标int _productor_step;int _consumer_step;// 3. 定义信号量sem_t _room_sem; // 生产者关心sem_t _data_sem; // 消费者关心// 4. 定义锁,维护多生产多消费之间的互斥关系pthread_mutex_t _productor_mutex;pthread_mutex_t _consumer_mutex;
};

5.4.多生产和多消费的并发性体现在:

消费者在处理任务的时候可以并发,

所以多生产和多消费的意义不在于向队列中生产,再从队列中拿走。而在于生产前我们可以多线程并发获取原始任务,生产后,被我们的消费者拿走任务后,可以多线程并发式的去执行各自的任务。这才是多生产多消费的意义

多生产,多消费的模型主要在于,多个生产者去竞争一个名额然后进行加锁,多个消费者竞争一个名额然后进行加锁,所以最终还是会变成单生产,单消费

相关文章:

【Linux】从多线程同步到生产者消费者模型:多线程编程实践

目录 1.线程的同步 1.1.为什么需要线程的同步&#xff1f; 2.2.条件变量的接口函数 2.生产消费模型 2.1 什么是生产消费模型 2.2.生产者消费者模型优点 2.3.为何要使用生产者消费者模型 3.基于BlockingQueue的生产者消费者模型 3.1为什么要将if判断变成while&#xff…...

如何在word里面给文字加拼音?

如何在word里面给文字加拼音&#xff1f;在现代社会&#xff0c;阅读已经成为了我们日常生活中不可或缺的一部分。尤其是在学习汉语的过程中&#xff0c;拼音的帮助显得尤为重要。为了帮助大家更好地理解和掌握汉字的发音&#xff0c;许多教师和学生都希望能够在Word文档中为文…...

Detr论文精读

摘要&#xff1a; 作者提到&#xff0c;该方法将物体检测看做直接的集合预测&#xff0c;在传统的目标检测算法中&#xff0c;会先生成候选区域&#xff0c;然后对每个候选区域进行单独的预测&#xff08;包括物体的分类和预测框的回归&#xff09;&#xff0c;集合预测就是直…...

找寻孤独伤感视频素材的热门资源网站推荐

在抖音上&#xff0c;伤感视频总是能够引起观众的共鸣&#xff0c;很多朋友都在寻找可以下载伤感视频素材的地方。作为一名资深的视频剪辑师&#xff0c;今天我来分享几个提供高清无水印伤感素材的网站&#xff0c;如果你也在苦苦寻找这些素材&#xff0c;不妨看看以下推荐&…...

大模型~合集13

我自己的原文哦~ https://blog.51cto.com/whaosoft/12302606 #TextRCNN、TextCNN、RNN 小小搬运工周末也要学习一下~~虽然和世界没关 但还是地铁上看书吧, 大老勿怪 今天来说一下 文本分类必备经典模型 模型 SOTA&#xff01;模型资源站收录情况 模型来源论文 RAE ​​ht…...

【Next.js 项目实战系列】04-修改 Issue

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c;给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 项目实战系列】03-查看 Issue 修改 Issue 添加修改 Button​ 本节代码链接 安装 Radix UI 的 Ra…...

【Linux】并行与并发(含时间片)

简单来说 并发&#xff1a;多个进程轮流使用同一个CPU&#xff0c;在逻辑层面上&#xff0c;一段时间内推进完成了多个进程 并行&#xff1a;机器中有多个CPU可以使用&#xff0c;在物理层面上&#xff0c;做到同一时间会有多个进程同时在运行 举个例子&#xff1a;一群人需要…...

【Flutter】页面布局:弹性布局(Flex)

在 Flutter 开发中&#xff0c;布局是非常重要的部分。布局系统允许开发者控制和管理界面上的组件如何排列和展示。弹性布局&#xff08;Flex&#xff09;是其中一个非常强大且常用的布局组件&#xff0c;它能够在水平方向或垂直方向上灵活调整子组件的空间分配比例。Row 和 Co…...

深入解析 Go 语言接口:多接口实现与接口组合的实际应用

文章目录 一、引言二、一个类型实现多个接口1. 定义多个接口2. 类型实现多个接口3. 使用多个接口 三、接口的组合1. 接口嵌套2. 实现复合接口 四、实际开发中的应用场景1. 多态与模块化设计2. 松耦合系统设计3. 测试与依赖注入4. 事件驱动架构中的应用 五、小结 一、引言 在 G…...

Eclipse——Java开发详解

Eclipse 1、配置JDK2、设置编译版本2.1、全局编译版本2.2、项目编译版本2.3、Web项目编译版本 3、设置工作目录4、创建Java项目5、配置Tomcat6、创建Web项目7、配置Maven8、创建Maven项目8.1、普通Maven项目8.2、Maven Web项目 9、创建SpringBoot项目10、设置字体11、设置代码提…...

练手小项目推荐

以下是一些练手项目推荐&#xff0c;我可以给你一些适合学生毕业设计的小项目建议&#xff0c;既可以锻炼技能&#xff0c;也能完成学术要求。以下是一些可行的毕业设计项目建议&#xff1a; 校园导航APP 功能&#xff1a;为校园内的新生和访客提供导航&#xff0c;标记教室、…...

一图秒懂色彩空间和色彩模型

色彩空间和色彩模型 想必学过图像处理或者摄影的小伙伴都知道这两个词&#xff0c;看了一些博客&#xff0c;发现很少有人把这两个概念说清楚的&#xff0c;大多数都是混在一起&#xff0c;色彩模型和色彩空间的概念混为一谈&#xff0c;很让人疑惑。   这里我们用一张图来解…...

控制Stable Diffusion生成质量的多种方法

在Stable Diffusion绘图中&#xff0c;控制AI生成图像的质量可以通过多种方法来实现。以下是几种常见的方法&#xff1a; 1. 从底模控制&#xff08;Checkpoint&#xff09; 使用不同的模型检查点&#xff08;Checkpoints&#xff09;可以显著影响生成图像的质量和细节。选择一…...

递归算法笔记

根据b站视频整理的 **视频地址&#xff1a;**https://www.bilibili.com/video/BV1S24y1p7iH/?spm_id_from333.788.videopod.sections&vd_source6335ddc7b30e1f4510569db5f2506f20 最常见的一个递归例子&#xff1a; 斐波那契数列&#xff1a;1&#xff0c;2&#xff0c;3…...

Android——发送彩信

跳转到相册选择图片 btn_jump.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {// 跳转到系统相册选择图片并返回Intent intent new Intent(Intent.ACTION_GET_CONTENT);// 设置图片类型为图片类型intent.setType("image/*&quo…...

对比迁移项目的改动

文章目录 对比迁移项目的改动场景背景解决方案 对比迁移项目的改动 场景背景 同源定制化项目&#xff0c;同一套代码扩展出来的项目&#xff08;从领导口中得知&#xff09; A项目的有三维地图展示&#xff0c;项目B跑起来却加载不出来&#xff0c;但是本地运行A项目代码&…...

数据结构-复杂度

复杂度 1.数据结构1.1算法 2.算法效率2.1复杂度的概念 3.时间复杂度3.1大O渐进表示法3.2时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例5&#xff1a;3.2.6 示例63.2.7 示例7 4.空间复杂度4.1.1 示例14.1.2 示例2 5.常见复杂度对比6.复杂度算法题6…...

无人机之放电速率篇

无人机的放电速率是指电池在一定时间内放出其储存电能的能力&#xff0c;这一参数对无人机的飞行时间、性能以及安全性都有重要影响。 一、放电速率的表示方法 放电速率通常用C数来表示。C数越大&#xff0c;表示放电速率越快。例如&#xff0c;一个2C的电池可以在1/2小时内放…...

免费开源AI助手,颠覆你的数字生活体验

Apt Full作为一款开源且完全免费的软件&#xff0c;除了强大的自然语言处理能力&#xff0c;Apt Full还能够对图像和视频进行一系列复杂的AI增强处理&#xff0c;只需简单几步即可实现专业级的效果。 在图像处理方面&#xff0c;Apt Full提供了一套全面的AI工具&#xff0c;包…...

VMware虚拟机三种网络模式详解

主要内容 1. 桥接模式2. NAT模式VMware Network Adapter VMnet8虚拟网卡的作用 3. 仅主机模式VMware Network Adapter VMnet1虚拟网卡的作用设置虚拟机联通外网 4. 总结 参考资料&#xff1a; 1.Vmware虚拟机三种网络模式详解 VMware虚拟机三种网络模式详解之Bridged&#xff0…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...