【linux学习指南】模拟线程封装与智能指针shared_ptr
文章目录
- 📝线程封装
- 🌉 Thread.hpp
- 🌉 Makefile
- 🌠线程封装第一版
- 🌉 Makefile:
- 🌉Main.cc
- 🌉 Thread.hpp:
- 🌠线程封装第二版
- 🌉 Thread.hpp:
- 🌉 Main.cc
- 🌠单线程创建测试
- 🌉 Thread.hpp
- 🌉 main.cc
- 🌠智能指针std::shared_ptr
- 🚩总结
📝线程封装
🌉 Thread.hpp
// Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
namespace ThreadModule
{// 原⼦计数器,⽅便形成线程名称std::uint32_t cnt = 0;// 线程要执⾏的外部⽅法,我们不考虑传参,后续有std::bind 来进⾏类间耦合using threadfunc_t = std::function<void()>;// 线程状态enum class TSTATUS{THREAD_NEW,THREAD_RUNNING,THREAD_STOP};// 线程class Thread{private:static void *run(void *obj){Thread *self = static_cast<Thread *>(obj);pthread_setname_np(pthread_self(), self->_name.c_str()); // 设置线程名称self->_status = TSTATUS::THREAD_RUNNING;if (!self->_joined){pthread_detach(pthread_self());}self->_func();return nullptr;}void SetName(){// 后期加锁保护_name = "Thread-" + std::to_string(cnt++);}public:Thread(threadfunc_t func) : _status(TSTATUS::THREAD_NEW),_joined(true), _func(func){SetName();}void EnableDetach(){if (_status == TSTATUS::THREAD_NEW)_joined = false;}void EnableJoined(){if (_status == TSTATUS::THREAD_NEW)_joined = true;}bool Start(){if (_status == TSTATUS::THREAD_RUNNING)return true;int n = ::pthread_create(&_id, nullptr, run, this);if (n != 0)return false;return true;}bool Join(){if (_joined){int n = pthread_join(_id, nullptr);if (n != 0)return false;return true;}return false;}~Thread() {}private:std::string _name;pthread_t _id;TSTATUS _status;bool _joined;threadfunc_t _func;};
}
🌉 Makefile
// main.cc
#include <iostream>
#include <unistd.h>
#include "Thread.hpp"
void hello1()
{char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer) - 1);while (true){}std::cout << "hello world, " << buffer << std::endl;sleep(1);
}
void hello2()
{char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer) - 1);while (true){std::cout << "hello world, " << buffer << std::endl;sleep(1);}
}
int main()
{pthread_setname_np(pthread_self(), "main");ThreadModule::Thread t1(hello1);t1.Start();ThreadModule::Thread t2(std::bind(&hello2));t2.Start();t1.Join();t2.Join();return 0;
}
运⾏结果查询
$ ps -aLPID LWP TTY TIME CMD
195828 195828 pts/1 00:00:00 main
195828 195829 pts/1 00:00:00 Thread-0
195828 195830 pts/1 00:00:00 Thread-1
🌠线程封装第一版
🌉 Makefile:
bin=testThread
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc$(cc) -c $< -std=c++17.PHONY:test
test:echo $(src)echo $(obj)
🌉Main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>// using thread_ptr_t = std::shared_ptr<ThreadModule::Thread>;#define NUM 10;class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}int main()
{threadData td;td.max = 60;td.start = 50;//使用lamda表达式封装Count成一个不接受参数的可调用对象auto func = [td](){Count(td);};ThreadModule::Thread<threadData> t(func);t.Start();t.Join();return 0;
}
🌉 Thread.hpp:
//V1
namespace ThreadModule
{// template<typename T>using func_t = std::function<void()>;static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{private:static void* Routine(void *args){Thread* t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func();return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func): _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread - " + std::to_string(number++);_pid = getpid();}bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routine, this); // 这里使用thisif (n != 0)return false;return true;}return true;}bool Stop(){if (_status != TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP; return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP; return true; }return false;}void Detach(){EnableDetach();pthread_detach(_tid);}bool IsJoinable(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;pid_t _pid;bool _joinable; // 是否是分离的, 默认不是func_t _func;TSTATUS _status;};
}#endif
🌠线程封装第二版
🌉 Thread.hpp:
#ifndef THREAD_HPP
#define THREAD_HPP#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>//V2
namespace ThreadModule
{static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{using func_t = std::function<void(T)>;private://成员方法static void *Routine(void* args){Thread<T> *t = static_cast<Thread<T>*>(args);t->_status = TSTATUS::RUNNING;t->_func(t->_data);return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func, T data):_func(func),_data(data),_status(TSTATUS::NEW),_joinable(true){_name = "Thread -" +std::to_string(number++);_pid = getpid();}bool Start(){if(_status != TSTATUS::RUNNING){int n = pthread_create(&_tid, nullptr, Routine, this);if(n != 0)return false;return true;}return false;}bool Stop(){if(_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool IsJoinale(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _joinable;//是否是分离的,默认不是func_t _func;pid_t _pid;TSTATUS _status;T _data;};
}#endif
🌉 Main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;#define NUM 10class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}
int main()
{//先描述再组织std::unordered_map<std::string, thread_ptr_t> threads;//如果我们要创建多线程呢?for(int i = 0; i< NUM ; i++){auto func = [](){while(true){std::cout<< "hello world" << std::endl;sleep(1);}};int threadData = i+1; thread_ptr_t t= std::make_shared<ThreadModule::Thread<int>>([func](int){func();}, threadData);std::cout<< "Create thread with name : "<<t->Name() <<std::endl;threads[t->Name()] = t;}for(auto &thread : threads){thread.second->Start();}for(auto &thread : threads){thread.second->Join();}
🌠单线程创建测试
🌉 Thread.hpp
#ifndef THREAD_HPP
#define THREAD_HPP#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>//V2
namespace ThreadModule
{static int number = 1;enum class TSTATUS{NEW,RUNNING,STOP};template<typename T>class Thread{using func_t = std::function<void(T)>;private://成员方法static void *Routine(void* args){Thread<T> *t = static_cast<Thread<T>*>(args);t->_status = TSTATUS::RUNNING;t->_func(t->_data);return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t func, T data):_func(func),_data(data),_status(TSTATUS::NEW),_joinable(true){_name = "Thread -" +std::to_string(number++);_pid = getpid();}bool Start(){if(_status != TSTATUS::RUNNING){int n = pthread_create(&_tid, nullptr, Routine, this);if(n != 0)return false;return true;}return false;}bool Stop(){if(_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if(_joinable){int n = ::pthread_join(_tid, nullptr);if(n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool IsJoinale(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _joinable;//是否是分离的,默认不是func_t _func;pid_t _pid;TSTATUS _status;T _data;};
}
🌉 main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;#define NUM 10class threadData
{
public:int max;int start;
};void Count(threadData td)
{for(int i = td.start; i < td.max; i++){std::cout<< "i == " <<i <<std::endl;sleep(1);}
}int main()
{auto func = [](){while(true){std::cout<< "hello world" <<std::endl;sleep(1);}};std::function<void(int)> wrappedFunc2 = [func](int){ func(); };int signalTreadData = 5;ThreadModule::Thread<int> t( wrappedFunc2,signalTreadData);// std::cout<< "Create thread with name : "<<t->Name() <<std::endl;// t.Start();// t.Join();t.Start();std::cout<< t.Name() << " is running" <<std::endl;std::cout<<std::flush;//手动刷新缓冲期sleep(5);if (t.Stop()) {std::cout << "Stop thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区} else {std::cout << "Failed to stop thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区}sleep(1);if (t.Join()) {std::cout << "Join thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区} else {std::cout << "Failed to join thread : " << t.Name() << std::endl;std::cout << std::flush; // 手动刷新缓冲区}return 0;
}
🌠智能指针std::shared_ptr
std::shared_ptr
是 C++ 标准库 <memory>
头文件中提供的一种智能指针,用于管理动态分配的对象,它实现了共享所有权的语义,下面为你详细介绍它的作用、工作原理以及在你给出的代码中的使用场景。
作用
在传统的 C++ 中,使用 new
操作符动态分配内存后,需要手动使用 delete
操作符释放内存,否则会导致内存泄漏。std::shared_ptr
可以自动管理动态分配的对象的生命周期,当没有任何 std::shared_ptr
指向该对象时,它会自动释放对象所占用的内存,从而避免了手动管理内存带来的复杂性和潜在的内存泄漏问题。
工作原理
std::shared_ptr
使用引用计数的机制来管理对象的生命周期。每个 std::shared_ptr
都维护一个引用计数,记录有多少个 std::shared_ptr
共享同一个对象。当一个新的 std::shared_ptr
指向一个对象时,引用计数加 1;当一个 std::shared_ptr
被销毁或者指向其他对象时,引用计数减 1。当引用计数变为 0 时,说明没有任何 std::shared_ptr
再指向该对象,此时 std::shared_ptr
会自动调用对象的析构函数并释放内存。
-
using thread_ptr_t = std::shared_ptr<ThreadModule::Thread<int>>;
这行代码使用using
关键字定义了一个类型别名thread_ptr_t
,它实际上是std::shared_ptr<ThreadModule::Thread<int>>
的别名。这样做的好处是可以简化代码,避免在后续使用时多次书写冗长的类型名。这里的ThreadModule::Thread<int>
是一个模板类的实例化,表示一个线程对象,std::shared_ptr
用于管理这个线程对象的生命周期。 -
std::unordered_map<std::string, thread_ptr_t> threads;
这行代码定义了一个std::unordered_map
,它是一个无序关联容器,用于存储键值对。键的类型是std::string
,值的类型是thread_ptr_t
,也就是std::shared_ptr<ThreadModule::Thread<int>>
。通过这种方式,可以将线程对象与一个字符串键关联起来,方便对线程对象进行管理和查找。 -
thread_ptr_t t = std::make_shared<ThreadModule::Thread<int>>( ... );
这行代码使用std::make_shared
函数创建了一个std::shared_ptr<ThreadModule::Thread<int>>
对象,并将其赋值给t
。std::make_shared
是一个便捷的函数,用于创建std::shared_ptr
对象,它会在一次内存分配中同时分配对象和引用计数所需的内存,比分别使用new
和std::shared_ptr
的构造函数更加高效。括号内的参数是传递给ThreadModule::Thread<int>
构造函数的参数,用于初始化线程对象。
示例代码
#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; }
};int main() {// 创建一个 std::shared_ptr 对象std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();// 复制一个 std::shared_ptr 对象,引用计数加 1std::shared_ptr<MyClass> ptr2 = ptr1;// 调用对象的成员函数ptr1->doSomething();// 当 ptr1 和 ptr2 离开作用域时,引用计数减 1// 当引用计数变为 0 时,对象会被自动销毁return 0;
}
代码解释
- 在
main
函数中,首先使用std::make_shared
创建了一个std::shared_ptr<MyClass>
对象ptr1
,此时引用计数为 1。 - 然后将
ptr1
赋值给ptr2
,引用计数变为 2。 - 调用
ptr1->doSomething()
来调用对象的成员函数。 - 当
ptr1
和ptr2
离开main
函数的作用域时,它们会被销毁,引用计数减 1。当引用计数变为 0 时,MyClass
对象的析构函数会被自动调用,释放对象所占用的内存。
🚩总结
如果要像C++11那样进⾏可变参数的传递,是可以这样设计的,但是太⿇烦了,真到了哪⼀步,就直接⽤c++11吧,我们的⽬标主要是理解系统概念对象化,此处不做复杂设计,⽽且后续可以使⽤std::bind来进⾏对象间调⽤
相关文章:

【linux学习指南】模拟线程封装与智能指针shared_ptr
文章目录 📝线程封装🌉 Thread.hpp🌉 Makefile 🌠线程封装第一版🌉 Makefile:🌉Main.cc🌉 Thread.hpp: 🌠线程封装第二版🌉 Thread.hpp:🌉 Main.cc …...
10、Python面试题解析:解释reduce函数的工作原理
reduce 是 Python 中的一个高阶函数,位于 functools 模块中。它的作用是将一个可迭代对象(如列表、元组等)中的元素依次通过一个二元函数(即接受两个参数的函数)进行累积计算,最终返回一个单一的结果。 1.…...

【含开题报告+文档+PPT+源码】学术研究合作与科研项目管理应用的J2EE实施
开题报告 本研究构建了一套集注册登录、信息获取与科研项目管理于一体的综合型学术研究合作平台。系统用户通过注册登录后,能够便捷地接收到最新的系统公告和科研动态新闻,并能进一步点击查看详尽的新闻内容。在科研项目管理方面,系统提供强…...
MySQL主从复制过程,延迟高,解决应对策略
MySQL主从复制延迟高是常见的性能问题,通常由主库写入压力大、从库处理能力不足或配置不当导致。以下从原因定位、优化策略和高级解决方案三个维度提供系统性解决方法: 一、快速定位延迟原因 1. 查看主从同步状态 SHOW SLAVE STATUS\G关键字段…...
Deepseek模拟阿里面试——数据库
在模拟阿里面试时,数据库部分需要涵盖广泛的知识点,包括基础概念、事务管理、索引优化、数据库设计、高并发处理、分布式数据库等。以下是对这些问题的详细分析和解答: 事务的ACID特性是什么,如何保证? ACID特性&…...

大数据学习之SparkStreaming、PB级百战出行网约车项目一
一.SparkStreaming 163.SparkStreaming概述 Spark Streaming is an extension of the core Spark API that enables scalable, high-throughput, fault-tolerant stream processing of live data streams. Spark Streaming 是核心 Spark API 的扩展,支持实时数据…...
Java 高频面试闯关秘籍
目录 Java基础篇:涵盖OOP、多线程、集合等基础知识。Java高级篇:深入探讨HashMap、JVM、线程池等高级特性。Java框架篇:介绍Spring、SpringMVC、MyBatis等常用框架。Mysql数据库篇:包含SQL语句、事务、索引等数据库知识。分布式技…...

边缘计算网关驱动智慧煤矿智能升级——实时预警、低延时决策与数字孪生护航矿山安全高效运营
迈向智能化煤矿管理新时代 工业物联网和边缘计算技术的迅猛发展,煤矿安全生产与高效运营正迎来全新变革。传统煤矿监控模式由于现场环境复杂、数据采集和传输延时较高,已难以满足当下高标准的安全管理要求。为此,借助边缘计算网关的实时数据…...
Oracle认证大师(OCM)学习计划书
Oracle认证大师(OCM)学习计划书 一、学习目标 Oracle Certified Master(OCM)是Oracle官方认证体系中的最高级别认证,要求考生具备扎实的数据库管理技能、丰富的实战经验以及解决复杂问题的能力。本计划旨在通过系统化的…...

力扣 单词拆分
动态规划,字符串截取,可重复用,集合类。 题目 单词可以重复使用,一个单词可用多次,应该是比较灵活的组合形式了,可以想到用dp,遍历完单词后的状态的返回值。而这里的wordDict给出的是list&…...

如何在Linux中设置定时任务(cron)
在Linux系统中,定时任务是自动执行任务的一种非常方便的方式,常常用于定期备份数据、更新系统或清理日志文件等操作。cron是Linux下最常用的定时任务管理工具,它允许用户根据设定的时间间隔自动执行脚本和命令。在本文中,我们将详…...
C# ASP.NET核心特性介绍
.NET学习资料 .NET学习资料 .NET学习资料 在当今的软件开发领域中,C# ASP.NET凭借其强大的功能和丰富的特性,成为构建 Web 应用程序的重要技术之一。以下将详细介绍 C# ASP.NET的核心特性。 多语言支持 ASP.NET 支持多种语言进行开发,这使…...

Response 和 Request 介绍
怀旧网个人博客网站地址:怀旧网,博客详情:Response 和 Request 介绍 1、HttpServletResponse 1、简单分类 2、文件下载 通过Response下载文件数据 放一个文件到resources目录 编写下载文件Servlet文件 public class FileDownServlet exten…...
Spring常用注解和组件
引言 了解Spring常用注解的使用方式可以帮助我们更快速理解这个框架和其中的深度 注解 Configuration:表示该类是一个配置类,用于定义 Spring Bean。 EnableAutoConfiguration:启用 Spring Boot 的自动配置功能,让 Spring Boo…...
Spring中都应用了哪些设计模式?
好的!以下是您提到的八种设计模式在 Spring 中的简单示例: 1. 简单工厂模式 简单工厂模式通过传入参数来决定实例化哪个类。Spring 中的 BeanFactory 就是简单工厂模式的应用。 示例代码: // 1. 创建接口和具体实现类 public interface A…...

VSCode的安裝以及使用
c配置: 【MinGw-w64编译器套件】 https://blog.csdn.net/weixin_60915103/article/details/131617196?fromshareblogdetail&sharetypeblogdetail&sharerId131617196&sharereferPC&sharesourcem0_51662391&sharefromfrom_link Python配置: 【簡單ÿ…...

Datawhale 组队学习 Ollama教程 task1
一、Ollama 简介 比喻:Ollama 就像是一个“魔法箱子”,里面装满了各种大型语言模型(LLM)。你不需要懂复杂的魔法咒语(配置),只需要轻轻一按(一条命令),就能让…...
前端技术学习——ES6核心基础
1、ES6与JavaScript之间的关系 ES6是JavaScript的一个版本:JavaScript是基于ECMAScript规范实现的编程语言,而ES6(ECMAScript 2015)是该规范的一个具体版本。 2、ES6的基础功能 (1)let和const let用于声明…...

《DeepSeek技术应用与赋能运营商办公提效案例实操落地课程》
大模型算法实战专家—周红伟老师 法国科学院数据算法博士/曾任阿里巴巴人工智能专家/曾任马上消费企业风控负责人 课程背景 随着大模型技术的迅猛发展,企业面临着提升工作效率、降低运营成本和优化资源配置的巨大压力。DeepSeek做出十三项革命性的大模型技术突破…...
STM32-知识
一、Cortex-M系列双指针 Cortex-M系列的MSP与PSP有一些重要的区别,双指针是为了保证OS的安全性和稳健性。本质上,区别于用户程序使用PSP,操作系统和异常事件单独使用一个MSP指针的目的,是为了保证栈数据不会被用户程序意外访问或…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...