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

【Linux多线程】生产者消费者模型

【Linux多线程】生产者消费者模型

目录

  • 【Linux多线程】生产者消费者模型
    • 生产者消费者模型
      • 为何要使用生产者消费者模型
      • 生产者消费者的三种关系
      • 生产者消费者模型优点
      • 基于BlockingQueue的生产者消费者模型
        • C++ queue模拟阻塞队列的生产消费模型
      • 伪唤醒情况(多生产多消费的情况下)

作者:爱写代码的刚子

时间:2024.3.29

前言:本篇博客将会介绍Linux多线程中一个非常重要的模型——生产者消费者模型

生产者消费者模型

  • 321原则(方便记忆):3种关系,2种角色(生产者和消费者),1个交易场所(特定结构的内存空间)

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

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

生产者消费者的三种关系

  • 生产者VS生产者 :互斥
  • 消费者VS消费者:互斥
  • 生产者VS消费者:互斥,同步

生产者消费者模型优点

  • 生产和消费进行解耦(多线程其实也是一种解耦)
  • 支持并发
  • 支持忙闲不均

在这里插入图片描述

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

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构(有点类似于管道)。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述

C++ queue模拟阻塞队列的生产消费模型

代码:

以下代码以单生产者,单消费者为例:

  • 代码一:
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
template <class T>
class BlockQueue
{static const int defalutnum = 5;
public:BlockQueue(int maxcap=defalutnum):maxcap_(maxcap){pthread_mutex_init(&mutex_,nullptr);pthread_cond_init(&c_cond_,nullptr);pthread_cond_init(&p_cond_,nullptr);}T pop(){pthread_mutex_lock(&mutex_);if(q_.size()== 0 ){pthread_cond_wait(&c_cond_,&mutex_);//生产和消费要使用不同的等待队列}T out = q_.front();q_.pop();pthread_cond_signal(&p_cond_);pthread_mutex_unlock(&mutex_);return out;}void push(const T &in){pthread_mutex_lock(&mutex_);if(q_.size()==maxcap_)//判断本身也是访问临界资源{pthread_cond_wait(&p_cond_,&mutex_);//调度时自动释放锁}//1.队列没满 2.被唤醒q_.push(in); pthread_cond_signal(&c_cond_);pthread_mutex_unlock(&mutex_);}~BlockQueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}
private:std::queue<T> q_;//我们不直接使用stl中的queue是因为它本身不是线程安全的,共享资源//int mincap_;int maxcap_;//队列中的极值pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;
};
#include "BlockQueue.hpp"
#include <unistd.h>
void *Consumer(void *args)
{BlockQueue<int> *bq =  static_cast<BlockQueue<int>*>(args); while(true){sleep(2);// 由于两个线程谁先执行是不确定的,我们让生产者先执行int data = bq->pop();std::cout<<"消费了一个数据: "<<data<<std::endl;}
}
void *Productor(void *args)
{BlockQueue<int> *bq =  static_cast<BlockQueue<int>*>(args); int data=0;while(true){++data;bq->push(data);std::cout<<"生产了一个数据: "<<data<<std::endl;}
}
int main()
{BlockQueue<int> *bq = new BlockQueue<int>(); pthread_t c,p;pthread_create(&c,nullptr,Consumer,bq);pthread_create(&p,nullptr,Productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}

在这里插入图片描述

  • 调整代码,使其生产者生产的数据到达一定范围通知消费者,消费者消费了一定的数据通知生产者:

在这里插入图片描述

在这里插入图片描述

【问】:生产者的数据从哪里来?

用户,或者网络等。生产者生产的数据也是要花时间获取的!,所以生产者要做两件事:1. 获取数据 2. 生产数据到队列

  • 同时消费者拿到数据要做加工处理,也要花时间!,消费者要做两件事:1. 消费数据 2. 加工处理数据

【问】:生产者消费者模型为什么是高效的?

存在一个线程访问临界区的代码,一个线程正在处理数据,高效并发。

虽然互斥和同步谈不上高效,更何况加了锁,但是一个线程正在生产数据,一个线程正在消费数据,两者解偶且互不影响。(在更多的生产者消费者情况下,只有少量的执行流在互斥和同步,而大量的执行流都在并发访问)

  • 再次完善代码,使该生产者消费者模型能够执行相应的任务:

BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
template <class T>
class BlockQueue
{static const int defalutnum = 20;
public:BlockQueue(int maxcap=defalutnum):maxcap_(maxcap){pthread_mutex_init(&mutex_,nullptr);pthread_cond_init(&c_cond_,nullptr);pthread_cond_init(&p_cond_,nullptr);low_water_ =maxcap_/3;high_water_ =(maxcap_*2)/3;}T pop(){pthread_mutex_lock(&mutex_);if(q_.size()== 0 ){pthread_cond_wait(&c_cond_,&mutex_);//生产和消费要使用不同的等待队列}T out = q_.front();q_.pop();if(q_.size()<low_water_){pthread_cond_signal(&p_cond_);}pthread_mutex_unlock(&mutex_);return out;}void push(const T &in){pthread_mutex_lock(&mutex_);if(q_.size()==maxcap_)//判断本身也是访问临界资源{pthread_cond_wait(&p_cond_,&mutex_);//调度时自动释放锁}//1.队列没满 2.被唤醒q_.push(in); if(q_.size()>high_water_){pthread_cond_signal(&c_cond_);}pthread_mutex_unlock(&mutex_);}~BlockQueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}private:std::queue<T> q_;//我们不直接使用stl中的queue是因为它本身不是线程安全的,共享资源//int mincap_;int maxcap_;//队列中的极值pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;int high_water_;int low_water_;
};

main.cc

#include "BlockQueue.hpp"
#include "Task.hpp"
#include <unistd.h>
void *Consumer(void *args)
{BlockQueue<Task> *bq =  static_cast<BlockQueue<Task>*>(args); while(true){Task t=bq->pop();//t.run();t();std::cout<<"处理任务: "<<t.GetTask()<<" 运算结果是: "<<t.GetResult()<<std::endl;//sleep(2);// 由于两个线程谁先执行是不确定的,我们让生产者先执行//std::cout<<"消费了一个数据: "<<data<<std::endl;}
}
void *Productor(void *args)
{int len = opers.size();BlockQueue<Task> *bq =  static_cast<BlockQueue<Task>*>(args); int data=0;while(true){int data1=rand()%10+1;usleep(10);int data2=rand() % 10;char op =opers[rand() % len];Task t(data1,data2,op);//++data;bq->push(t);//std::cout<<"生产了一个数据: "<<data<<std::endl;std::cout<<"生产了一个任务:"<< t.GetTask() <<std::endl;sleep(1);}
}
int main()
{srand(time(nullptr));BlockQueue<Task> *bq = new BlockQueue<Task>(); pthread_t c,p;pthread_create(&c,nullptr,Consumer,bq);pthread_create(&p,nullptr,Productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}

Task.hpp

#pragma once
#include <iostream>
#include <string>std::string opers = "+-*/%";enum
{DivZero = 1,ModZero,Unknown
};class Task
{
public:Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if (data2_ == 0)exitcode_ = DivZero;elseresult_ = data1_ / data2_;}break;case '%':{if (data2_ == 0)exitcode_ = ModZero;elseresult_ = data1_ % data2_;}break;default:exitcode_ = Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=";r += std::to_string(result_);r += "[code: ";r += std::to_string(exitcode_);r += "]";return r;}std::string GetTask(){std::string r = std::to_string(data1_);r+=oper_;r += std::to_string(data2_);r += "=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};

在这里插入图片描述

一定要记得,判断临界资源是否满足,也是在访问临界资源!!!

伪唤醒情况(多生产多消费的情况下)

多生产多消费的情况下:

举例:生产者只生产了一个数据,但是唤醒了多个消费者,多个消费者都在等待队列上,生产者将锁解开,多个消费者竞争这一把锁,其中一个消费者抢到了这把锁消费了一个数据,把锁解开,同时其他刚被唤醒的消费者其中又抢到了锁,进行消费,可是已经没有数据了(条件并不满足了),造成了伪唤醒的情况。(处于等待队列中的线程申请锁失败了会继续在条件变量中的等待队列中等)

或者说可能存在等待失败但是继续向下走的情况。

如何防止线程出现这种情况?

将if改成while(进行重复判断):

在这里插入图片描述

在这里插入图片描述

【问题】:无论是多生产多消费还是单生产单消费,本质上都是一个线程访问临界资源,那意义在哪?

重点是并发生产,并发消费,只是访问临界资源时是单个线程。重点不是获取数据本身,而在于处理数据!!!(本质)

相关文章:

【Linux多线程】生产者消费者模型

【Linux多线程】生产者消费者模型 目录 【Linux多线程】生产者消费者模型生产者消费者模型为何要使用生产者消费者模型生产者消费者的三种关系生产者消费者模型优点基于BlockingQueue的生产者消费者模型C queue模拟阻塞队列的生产消费模型 伪唤醒情况&#xff08;多生产多消费的…...

Django屏蔽Server响应头信息

一、背景 最近我们被安全部门的漏洞扫描工具扫出了一个服务端口的漏洞。这个服务本身是一个Django启动的web服务&#xff0c;并且除了登录页面&#xff0c;其它页面或者接口都需要进行登录授权才能进行访问。 漏洞扫描信息和提示修复信息如下: 自然这些漏洞如何修复&#xff0c…...

前端对数据进行分组和计数处理

js对数组数据的处理&#xff0c;添加属性&#xff0c;合并表格数据。 let data[{id:1,group_id:111},{id:2,group_id:111},{id:3,group_id:111},{id:4,group_id:222},{id:5,group_id:222} ]let tempDatadata; tempDatatempData.reduce((arr,item)>{let findarr.find(i>i…...

synchronized 和 lock

synchronized 和 Lock 都是 Java 中用于实现线程同步的机制&#xff0c;它们都可以保证线程安全。 # synchronized 介绍与使用 synchronized 可用来修饰普通方法、静态方法和代码块&#xff0c;当一个线程访问一个被 synchronized 修饰的方法或者代码块时&#xff0c;会自动获…...

ssh 公私钥(github)

一、生成ssh公私钥 生成自定义名称的SSH公钥和私钥对&#xff0c;需要使用ssh-keygen命令&#xff0c;这是大多数Linux和Unix系统自带的标准工具。下面&#xff0c;简单展示如何使用ssh-keygen命令来生成具有自定义名称的SSH密钥对。 步骤 1: 打开终端 首先&#xff0c;打开我…...

LangChain入门:8.打造自动生成广告文案的应用程序

在这篇技术博文中,我们将探讨如何利用LangChain框架的模板管理、变量提取和检查、模型切换以及输出解析等优势,打造一个自动生成广告文案的应用程序。 LangChain框架的优势 在介绍应用程序之前,让我们先了解一下LangChain框架的几个优势: 模板管理: 在大型项目中,文案可…...

AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#xff1a;设计模式深度解析&#xff1a;AI如何影响…...

【C语言环境】Sublime中运行C语言时MinGW环境的安装

要知道&#xff0c;GCC 官网提供的 GCC 编译器是无法直接安装到 Windows 平台上的&#xff0c;如果我们想在 Windows 平台使用 GCC 编译器&#xff0c;可以安装 GCC 的移植版本。 目前适用于 Windows 平台、受欢迎的 GCC 移植版主要有 2 种&#xff0c;分别为 MinGW 和 Cygwin…...

Ubuntu18.04 下Ublox F9P 实现RTK (利用CORS服务无需自建基站)

本内容参考如下连接:Ubuntu下Ublox F9P利用CORS服务无需自建基站实现RTK-CSDN博客 一、Ublox F9P 硬件模块示意图 图中展示了Ublox F9P的接口,包括串口2(`UART1`和`UART2`),USB1。需要人为通过u-center(Ublox F9P的显示软件)软件设置以下功能: Ublox通过`UART1`向PC端发送…...

springboot+vue在idea上面的使用小结

1.在mac上面删除java的jdk方法&#xff1a; sudo rm -rfjdk的路径 sudo rm -rf /Users/like/Library/Java/JavaVirtualMachines/corretto-17.0.10/Contents/Home 2.查询 Mac的jdk版本和路径&#xff1a; /usr/libexec/java_home -V 3.mac上面查询和关闭idea的网页端口&…...

MyEclipse将项目的开发环境与服务器的JDK 版本保持一致

前言 我们使用MyEclipse开发Java项目开发中&#xff0c;偶尔会遇到因项目开发环境不协调&#xff0c;导致这样那样的问题&#xff0c;在这里以把所有环境调整为JDK1.6 为例。 操作步骤 1.Window-->Preferences-->Java-->Installed JRES 修改为 1.6版本 2.Window-->…...

为BUG编程:函数重载的烦恼 char *匹配bool而不是string

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 这是一个BUG。 运行环境为linu…...

C++第十四弹---模板初阶

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、泛型编程 2、函数模板 2.1、函数模板的概念 2.2、函数模板的格式 2.3、函数模板的原理 2.4、函数模板的实例化 2.5、模板参数的匹配原则 …...

C++--内联函数

当调用一个函数时&#xff0c;程序就会跳转到该函数&#xff0c;函数执行完毕后&#xff0c;程序又返回到原来调用该函数的位置的下一句。 函数的调用也需要花时间&#xff0c;C中对于功能简单、规模小、使用频繁的函数&#xff0c;可以将其设置为内联函数。 内联函数&#xff…...

java数组与集合框架(一) -- 数据结构,数组

数据结构 概述 为什么要讲数据结构&#xff1f; 任何一个有志于从事IT领域的人员来说&#xff0c;数据结构&#xff08;Data Structure&#xff09;是一门和计算机硬件与软件都密切相关的学科&#xff0c;它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储…...

React 应用实现监控可观测性最佳实践

前言 React 是一个用于构建用户界面的 JavaScript 框架。它采用了虚拟 DOM 和 JSX&#xff0c;提供了一种声明式的、组件化的编程模型&#xff0c;以便更高效地构建用户界面。无论是简单还是复杂的界面&#xff0c;React 都可以胜任。 YApi 是使用 React 编写的高效、易用、功…...

批处理(Batch)把Excel文件xls格式和xlsx格式进行互换

批处理&#xff08;Batch&#xff09;把Excel文件xls格式改成xlsx格式以及xlsx格式改为xls格式。 Case1:xls转xlsx - 单个文件.bat $Excel New-Object -ComObject Excel.Application $Excel.Visible $false $Workbook $Excel.Workbooks.Open("C:\Test\Excel\1.xls&qu…...

Adobe ColdFusion 任意文件读取漏洞复现(CVE-2024-20767)

0x01 产品简介 Adobe ColdFusion是美国奥多比(Adobe)公司的一套快速应用程序开发平台。该平台包括集成开发环境和脚本语言,将可扩展、改变游戏规则且可靠的产品的愿景变为现实。 0x02 漏洞概述 由于 Adobe ColdFusion 的访问控制不当,未经身份认证的远程攻击者可以构造恶…...

搜索与图论——Floyd算法求最短路

floyd算法用来求多源汇最短路 用邻接矩阵来存所有的边 时间复杂度O(n^3) #include<iostream> #include<cstring> #include<algorithm>using namespace std;const int N 20010,INF 1e9;int n,m,k; int g[N][N];void floyd(){for(int k 1;k < n;k ){f…...

春招冲刺百题计划--矩阵篇

289. 生命游戏 题目&#xff1a; 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &#xff08;live&#xff09;&#xff0c;或 0 即为 死细胞 &#xff08;dead&#xff09;。每个细胞与…...

LLM大语言模型(八):ChatGLM3-6B使用的tokenizer模型BAAI/bge-large-zh-v1.5

背景 BGE embedding系列模型是由智源研究院研发的中文版文本表示模型。 可将任意文本映射为低维稠密向量&#xff0c;以用于检索、分类、聚类或语义匹配等任务&#xff0c;并可支持为大模型调用外部知识。 BAAI/BGE embedding系列模型 模型列表 ModelLanguageDescriptionq…...

MySQL中的三种日志

MySQL 包括三种类型的⽇志&#xff0c;分别是 binlog、 redolog 和 undolog&#xff0c;它们分别有不同的作⽤和特点。 binlog &#xff08;存档日志&#xff09; binlog&#xff08;Binary log&#xff09;是 MySQL 中的⼆进制⽇志⽂件&#xff0c;是 Server 层⽣成的的⽇志…...

Codeforces Round 932 (Div. 2)(A,B,C,D)

比赛链接 AB都是思维&#xff0c;更确切地说&#xff0c;A考了字符串字典序&#xff0c;很经典的贪心考点&#xff0c;B考了MEX运算。C出的还是比较好的&#xff0c;dp方法值得学习。D题是个不太好想的容斥&#xff0c;主要是变量有点多&#xff0c;容易搞混。 A. Entertainme…...

初识C++ · 入门(2)

目录 1 引用 1.1引用的概念 1.2 引用的特性 2 传值&#xff0c;传引用的效率 3 引用和指针的区别 4 内联函数 4.1 内联函数的定义 4. 2 内联函数的特性 5 关键字auto 5.1关于命名的思考 5.2 关于auto的发展 5.3 auto使用规则 6 范围for的使用 7 空指针 1 引用 …...

【opencv】教程代码 —ShapeDescriptors

检测和显示图像的轮廓 在图像中搜索并显示轮廓边缘多边形、轮廓矩形和包围圆 获取包含检测到的轮廓的椭圆和旋转的矩形 图像轮廓检测和轮廓凸包 计算图像中的轮廓的矩&#xff08;包括面积、重心等&#xff09;并进行显示 创建和绘制一个多边形图像然后计算并显示图像上每个点到…...

2024-03-28 Java8之Collectors类

Collectors类常用方法 文章目录 Collectors类常用方法1.toList、toSet、toMap2.joining、counting、summingInt、minBy3.groupingBy 1.toList、toSet、toMap Collector<T, ?, List<T>> toList(); //收集为List集合 Collector<T, ?, Set<T>> toSet()…...

第116讲:使用Mycat-eye管理Mycat数据库服务

文章目录 1.Mycat的管理工具2.Mycat-eye介绍3.部署Mycat-eye3.1.安装Zookeep3.2.安装Mycat-eye3.3.访问Mycat-eye 4.在Mycat-eye中导入Mycat服务的信息 1.Mycat的管理工具 Mycat默认开通2个端口&#xff0c;可以在server.xml中进行修改。 8066 数据访问端口&#xff0c;即进行…...

XR虚拟直播间,引领创新风潮,打破直播局限!

随着互联网技术日新月异的发展&#xff0c;直播行业也迎来了蓬勃发展的春天。然而&#xff0c;大多数直播间在吸引观众眼球和延长用户观看时长方面&#xff0c;仍然面临着巨大的挑战。正是在这样的背景下&#xff0c;XR虚拟直播系统应运而生&#xff0c;以其多维度的直播场景、…...

unity双层滑动实现

实现功能&#xff1a; 当滑动列表中内容处于顶端的时候&#xff0c;向上滑动优先滑动整个滑动列表&#xff0c;当滑动列表移动到设置位置&#xff0c;即设定的最高处时&#xff0c;继续移动列表内内容。向下移动亦然&#xff0c;当内容处于滑动列表顶端时&#xff0c;移动整个滑…...

浅谈AI技术创业有哪些机会?

一、AI技术创业概念简介 AI技术创业指的是利用人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;技术进行创业活动。人工智能是指计算机系统能够模拟和展现出人类智能的一种技术。在AI技术创业中&#xff0c;创业者利用AI技术来解决现实生活中的问题&…...

给宝宝做衣服网站好/微信推广

startsWith()方法 startsWith()方法用来判断当前字符串是否是以另外一个给定的子字符串“开头”的&#xff0c;根据判断结果返回 true 或 false 参数: str.startsWith(searchString [, position]) searchString 要搜索的子字符串 position 在 str 中搜索 searchString 的…...

做三国的网站/深圳网络营销推广招聘网

【解决方法】&#xff1a; 更改build/utils.js文件中的 ExtractTextPlugin 的 options配置. if (options.extract) {return ExtractTextPlugin.extract({use: loaders,publicPath: ../../, //注意: 此处根据路径, 自动更改fallback: vue-style-loader}) } else {return [vue-st…...

苹果手机怎么做ppt下载网站/网络营销大赛策划书

jQuery UI 设计主题文件结构主题是以特定的方式来增加他们的易用性。通常&#xff0c;文件目录结构如下所示&#xff1a;themename/ – 您的主题必须完全包含在一个单独的以主题名称命名的文件夹内。themename/themename.css – 这是基本的 CSS 文件。无论使用了哪个插件&#…...

wordpress 开启多用户/重庆网络seo公司

转载:https://blog.csdn.net/guyuebingchuan/article/details/19620487 1.在同一个网络内&#xff0c;连接电视的命令行&#xff1a; su stop adbd setprop service.adb.tcp.port 5555 start adbd adb connect 10.120.158.21 2.在一根网线时连接电视的命令行&#xff1a; 1.首…...

大鹏附近网站建设/好搜seo软件

Build Tools 即构建工具是一个把源代码生成可执行应用程序的过程自动化的程序&#xff08;例如Android app生成apk&#xff09;。由Apache软件基金会所提供。基于项目对象模型&#xff08;缩写&#xff1a;POM&#xff09;概念&#xff0c;Maven利用一个中央信息片断能管理一个…...

js网站模板怎么用/外链代发免费

最近Boss提了个需求&#xff0c;要收集下公司的电脑信息&#xff0c;配置比较低的淘汰掉。本来想用腾讯的电脑管家里的【硬件检测】工具&#xff0c;但也有些麻烦。它虽然可以将信息导出成txt文件&#xff0c;但录制作一张Excel表格就显得麻烦了&#xff0c;需要将每台电脑的硬…...