『 Linux 』线程池与 POSIX 线程的封装编码实现
文章目录
- 线程池概念
- 线程池的编码实现
- 线程池的测试
- 参考代码
- 线程的封装
- 使用测试封装后的线程
- 参考代码
线程池概念
池化技术是一种资源管理方法,通过预先创建和管理一组资源以便在需要使用时快速分配这些资源;
线程池是池化技术的一种典型应用;
-
资源分配
在线程池中预先创建一定数量的线程,并使其运行;
-
资源复用
在线程池中完成的线程不会被销毁,而是返回池中等待下一个任务;
-
资源管理
在线程池中统一管理线程的创建分配和回收,以集中管理资源的生命周期,包括创建,分配,回收和销毁;
-
性能优化
线程池能够避免频繁创建和销毁线程的开销,从而提高整体效率;
-
资源限制
在线程池中一般需要限制最大线程数以防止系统资源耗尽;
-
可配置性
在线程池中可配置核心线程数,最大线程数,空闲线程存活时间等;
线程池本质上是一个生产者消费者模型的典型应用;
其中用户即为生产者,线程池中的线程即为消费者,通过调用线程池的代码并向线程传递任务信息使得线程池中的线程能够获取对应的任务并进行消费处理;
线程池的编码实现
#ifndef THREADPOOL_HPP
#define THREADPOOL_HPP
#include <pthread.h>
#include <unistd.h>#include <iostream>
#include <queue>
#include <string>
#include <vector>// 定义线程信息结构体,包含线程ID和名称
struct ThreadInfo {pthread_t tid; // 线程IDstd::string name; // 线程名称
};// 线程池类模板,T 为任务类型
template <class T>
class ThreadPool {const static int defaultnum = 7; // 默认线程数量public:// 线程同步相关方法void Lock() { pthread_mutex_lock(&mutex_); } // 加锁void Unlock() { pthread_mutex_unlock(&mutex_); } // 解锁void Wake() { pthread_cond_signal(&cond_); } // 唤醒等待的线程void Wait() { pthread_cond_wait(&cond_, &mutex_); } // 等待条件变量bool Isempty() { return tasks_.empty(); } // 检查任务队列是否为空// 根据线程ID获取线程名称std::string Getname(pthread_t tid) {for (const auto &ti : threads_) {if (ti.tid == tid) {return ti.name;}}return "null";}public:// 构造函数,初始化线程池ThreadPool(int volume = defaultnum) : threads_(volume) {pthread_mutex_init(&mutex_, nullptr); // 初始化互斥锁pthread_cond_init(&cond_, nullptr); // 初始化条件变量}// 启动线程池void Start() {int num = threads_.size();for (int i = 0; i < num; ++i) {threads_[i].name = "Thread-" + std::to_string(i); // 设置线程名称// 创建线程,并传入处理任务的静态函数pthread_create(&(threads_[i].tid), nullptr, HanderTask, this);}}// 向任务队列添加任务void Push(const T &t) {Lock(); // 加锁保护共享资源tasks_.push(t); // 添加任务到队列Wake(); // 唤醒等待的线程Unlock(); // 解锁}// 从任务队列取出任务T Pop() {T t = tasks_.front(); // 获取队首任务tasks_.pop(); // 移除队首任务return t;}// 静态任务处理函数static void *HanderTask(void *args) {// 将参数转换为线程池指针ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->Getname(pthread_self()); // 获取当前线程名称while (1) {tp->Lock();while (tp->Isempty()) {tp->Wait(); // 如果任务队列为空,则等待}T t = tp->Pop(); // 取出任务tp->Unlock();// 执行任务t();// 打印任务执行结果(这里假设任务类型 T 有特定的方法)printf("The %s handler a task ,the result is %2d %c %2d = %3d ,the exit code "":%d\n",name.c_str(), t.getnum1(), t.getoper(), t.getnum2(), t.getresult(),t.getexitcode());}return nullptr;}// 析构函数,清理资源~ThreadPool() {pthread_mutex_destroy(&mutex_); // 销毁互斥锁pthread_cond_destroy(&cond_); // 销毁条件变量}private:std::vector<ThreadInfo> threads_; // 存储线程信息的向量std::queue<T> tasks_; // 任务队列pthread_mutex_t mutex_; // 互斥锁,用于线程同步pthread_cond_t cond_; // 条件变量,用于线程同步
};#endif
以该段代码为例设计了一个建议的线程池,主要思路为使用POSIX
线程库通过预先创建一组线程和使用任务队列管理并发任务;
封装了一个ThreadInfo
结构体用于存储每个线程的基本信息包括线程的tid
与线程名;
// 定义线程信息结构体,包含线程ID和名称
struct ThreadInfo {pthread_t tid; // 线程IDstd::string name; // 线程名称
};
-
ThreadPool
类模板使用了模板参数
T
定义任务的类型,使得线程可以处理不同类型的任务;并将线程池中常用的操作
pthread_mutex_lock()
,pthread_mutex_unlock()
,pthread_cond_wait()
与pthread_cond_signal()
分装为了Lock()
,Unlock()
,Wait()
与Wake()
以方便后续在线程池中的编码;其中该线程池中设计了几个私有成员:
-
std::vector<ThreadInfo> threads_
用于存储线程基本信息的容器;
-
std::queue<T> tasks_
用于存储任务的任务队列;
-
pthread_mutex_t mutex_
声明了一个互斥锁,用于处理线程池中线程间的互斥;
-
pthread_cond_t cond_
声明了一个条件变量,用于处理线程池中线程间的同步关系;
-
const static int defaultnum = 7
设置了一个静态成员变量并初始化作为线程池默认情况下的线程数量;
同时定义了两个类内函数
Isempty()
与Getname()
分别用来检查任务队列状态与线程名称的获取; -
-
构造函数
在构造函数中初始化了线程向量和同步语句(互斥锁和条件变量);
-
Start()
函数该函数用于创建指定数量的线程,每个线程执行对应的
HanderTask()
函数;其中因为
HanderTask()
函数被static
声明为静态,无法直接调用类内成员函数,故需要在使用pthread_create()
函数时传参this
指针; -
Push()
函数与Pop()
函数分别用来向任务队列添加任务和从队列中取出任务,其中
Push()
的实现依靠互斥锁与条件变量使线程在进行该动作时保证其同步与互斥关系; -
static void* HanderTask(void* args)
该函数为线程主要的执行函数,为线程的执行入口;
使用静态
static
声明函数的主要原因是该函数用于设计为必须为void* (*) (void*)
的函数;而类内成员函数的第一个参数必定存在一个隐含的
this
指针,从而导致参数数量不匹配,故需要使用static
将其修饰为静态成员函数;对应的静态成员函数的特点为如下:
- 不依赖于类的实例
- 不能直接访问非静态成员变量或调用非静态成员函数
- 不隐含
this
指针
-
析构函数
析构函数用于负责清理同步语句;
在该代码仅供参考,在HanderTask()
函数中所执行的任务必须是定义的,即该处的T
应有特定的方法,线程将执行该T
类型特定的方法并打印对应信息;
线程池的测试
假设线程池的任务为一个Task
类所封装的任务:
/* Task.hpp */#ifndef TASK_HPP
#define TASK_HPP
#include <iostream>// 定义错误代码枚举
enum { DIV_ERR = 1, MOD_ERR, NONE };class Task {public:Task() {} // 默认构造// 便于环形生产者消费者模型能够进行默认构造初始化并进行默认拷贝构造// 构造函数:初始化所有成员变量Task(int num1, int num2, char oper): num1_(num1), num2_(num2), exit_code_(0), result_(0), oper_(oper) {}// 析构函数(当前为空)~Task() {}// 执行任务的主要函数void run() {switch (oper_) {case '+':result_ = num1_ + num2_;break;case '-':result_ = num1_ - num2_;break;case '*':result_ = num1_ * num2_;break;case '/': {if (num2_ == 0) {exit_code_ = DIV_ERR; // 设置除零错误result_ = -1; // 除零时结果设为-1} elseresult_ = num1_ / num2_;break;}case '%': {if (num2_ == 0) {exit_code_ = MOD_ERR; // 设置模零错误result_ = -1; // 模零时结果设为-1} elseresult_ = num1_ % num2_;break;}default:exit_code_ = NONE; // 未知操作符break;}}// 重载()运算符,使对象可以像函数一样被调用void operator()() { run(); }// 获取计算结果int getresult() { return result_; }// 获取退出代码int getexitcode() { return exit_code_; }// 获取第一个操作数int getnum1() { return num1_; }// 获取第二个操作数int getnum2() { return num2_; }// 获取操作符char getoper() { return oper_; }private:int num1_; // 第一个操作数int num2_; // 第二个操作数int exit_code_; // 退出代码,用于表示操作是否成功int result_; // 计算结果char oper_; // 操作符
};#endif
该段代码封装了一个简单的加减乘除取模任务用于测试线程池;
对应的测试代码如下:
#include "Task.hpp"
#include "ThreadPool.hpp"
using namespace std;int main() {srand(time(nullptr));ThreadPool<Task> *tp =new ThreadPool<Task>(); // 若参数传5 表示需要创建的线程池线程数量为5tp->Start(); // 运行线程池string opers = "+-*/%";int len = opers.size();while (true) {// 1. 构建任务int num1 = rand() % 15;usleep(10);int num2 = rand() % 15;char op = opers[rand() % len];Task task(num1, num2, op);// 2. 将任务发送给线程池使其进行处理tp->Push(task);printf("The main thread send a task: %d %c %d = ?\n", num1, op, num2);sleep(1);}return 0;
}
种下一个随机数种子;
使用new ThreadPool<Task>
实例化出一个线程池对象(默认为7
),并调用其Start()
函数将其运行;
定义了一个string
对象并初始化为"+-*/%"
便于模拟随机生成任务发送给线程池;
在while()
循环中不断创建新的任务并调用线程池中的Push()
接口发送给线程池让线程池进行处理任务并打印对应信息;
运行结果为:
$ ./threadpool
The main thread send a task: 5 % 6 = ?
The Thread-0 handler a task ,the result is 5 % 6 = 5 ,the exit code :0
The main thread send a task: 14 - 5 = ?
The Thread-1 handler a task ,the result is 14 - 5 = 9 ,the exit code :0
The main thread send a task: 3 % 6 = ?
The Thread-2 handler a task ,the result is 3 % 6 = 3 ,the exit code :0
The main thread send a task: 4 / 9 = ?
The Thread-3 handler a task ,the result is 4 / 9 = 0 ,the exit code :0
The main thread send a task: 12 + 7 = ?
The Thread-4 handler a task ,the result is 12 + 7 = 19 ,the exit code :0
The main thread send a task: 3 / 9 = ?
The Thread-5 handler a task ,the result is 3 / 9 = 0 ,the exit code :0
The main thread send a task: 14 * 1 = ?
The Thread-6 handler a task ,the result is 14 * 1 = 14 ,the exit code :0
The main thread send a task: 5 % 2 = ?
The Thread-0 handler a task ,the result is 5 % 2 = 1 ,the exit code :0
...
...
参考代码
(供参考) CSDN - Dio夹心小面包 / Gitee - 半介莽夫
线程的封装
一切皆为对象,对应的POSIX
线程pthread_t
也可封装为一个类,同时可通过拓展这个类使线程的使用更加便捷;
#ifndef THREAD_HPP
#define THREAD_HPP#include <pthread.h>
#include <ctime>
#include <iostream>
#include <string>// 定义回调函数类型
typedef void (*callback_t)();class Thread {private:static int num_; // 静态成员,用于给线程命名private:// 静态线程入口函数,符合 pthread_create 的要求static void *Routine(void *args) {Thread *thread = static_cast<Thread *>(args);thread->Entery(); // 调用实际的线程入口函数return nullptr;}public:// 构造函数,初始化线程对象Thread(callback_t cb): tid_(0), name_(""), start_timestamp_(0), isrunning_(false), cb_(cb) {}// 启动线程void Run() {name_ = "thread-" + std::to_string(num_++); // 设置线程名start_timestamp_ = time(nullptr); // 记录启动时间戳isrunning_ = true;pthread_create(&tid_, nullptr, Routine, this); // 创建线程}// 等待线程结束void Join() {pthread_join(tid_, nullptr);isrunning_ = false;}// 实际的线程入口函数,调用回调函数void Entery() { cb_(); }// 分离线程void Detach() {if (isrunning_) {pthread_detach(tid_);isrunning_ = false;}}// 析构函数,确保线程正确结束~Thread() {if (isrunning_) {Join();}}// 获取线程名std::string getName() { return name_; }// 获取线程启动时间戳uint64_t getStartTimeStamp() { return start_timestamp_; }// 检查线程是否正在运行bool isRunning() { return isrunning_; }private:pthread_t tid_; // 线程IDstd::string name_; // 线程名称uint64_t start_timestamp_; // 线程启动时间戳bool isrunning_; // 线程运行状态标志callback_t cb_; // 回调函数
};// 初始化静态成员变量
int Thread::num_ = 1;#endif
该代码为一个Thread
类的实现,封装了POSIX
线程的基本操作;
定义了几个成员函数分别为tid_
,name_
,start_timestamp_
,isrunning_
和cb_
,分别用来存储线程的tid
,线程名,线程启动时的时间戳,线程的运行状态以及用户传入的函数;
-
callback_t
定义了一个函数指针类型,用于存储线程要执行的回调函数;
-
静态成员
num_
该参数用于为每个线程生成唯一的名字,这里可能涉及到
num_
被所有线程共享,导致产生临界资源的竞争的问题; -
Routine()
该函数是线程的一个入口点,即
pthread_create()
函数的第三个参数;该函数被声明为一个静态函数,本质原因是因为
POSIX
标准定义传入的函数必须为void *(*)(void*)
类型的函数,而类内函数存在隐含的this
指针,故声明为静态函数;为了避免这个问题也可将将该函数防止与类外;
该函数将传入的参数转换回
Thread
对象指针,并调用该对象的Entery()
函数; -
构造函数
构造函数用于初始化线程对象,并设置回调函数;
-
Run()
该函数用于设置线程名称和启动时间戳,并调用
pthread_create()
创建实际的POSIX
线程;由于该函数需配合
Routine()
函数故在调用pthread_create()
时需传入自身的this
指针; -
Join()
该函数用于等待线程结束,并更新运行状态;
-
Entery()
该函数为函数的入口点,本质上在
Run()
中调用pthread_create()
传入对应的Routine()
函数时Routine()
函数将调用类内封装的Entery()
函数,而Entery()
函数将直接通过调用cb_()
来运行;其中
cb_
本质上赋的就是用户所传入的函数指针; -
Detach()
该方法用于用户选择是否将该线程进行分离;
当分离过后即不可再对其进行
Join()
操作; -
析构函数
析构函数确保线程在对象销毁时正常结束;
-
其他方法
提供了获取线程名称,启动时间戳和运行状态等接口;
使用测试封装后的线程
#include <unistd.h>
#include <iostream>
#include <vector>
#include "Thread.hpp"using namespace std;// 线程执行的函数
void run() {while (1) {cout << "thread running" << endl;sleep(1); // 每秒打印一次}
}int main() {// 创建一个存储Thread对象的vectorvector<Thread> threads;// 创建5个Thread对象,每个对象都使用run函数作为回调for (int i = 0; i < 5; ++i) {threads.push_back(Thread(run));}// 启动所有线程for (auto& th : threads) {th.Run();}// 等待所有线程结束// 注意:由于run函数中是无限循环,这些线程实际上不会结束for (auto& th : threads) {th.Join();}return 0;
}
以该段代码为例,定义了一个run()
函数作为线程的执行函数;
这个函数将会无限循环打印一次thread running
;
在main()
函数中创建了一个vector<Thread>
来存储Thread
对象,并创建5
个对象都调用run()
作为回调;
启动所有的创建的线程;
可用shell
脚本进行观察:
$ while : ; do ps -aL | head -1 && ps -aL | grep mythread ; echo "------------------------------------" ; sleep 1 ;done
最终运行结果为:
# 程序所在会话$ ./mythread
thread runningthread running
thread running
thread running
thread runningthread runningthread running
thread runningthread running
thread running
thread running
thread running
thread running
thread running
thread running
...
...# shell 脚本会话PID LWP TTY TIME CMD8639 8639 pts/0 00:00:00 mythread8639 8640 pts/0 00:00:00 mythread8639 8641 pts/0 00:00:00 mythread8639 8642 pts/0 00:00:00 mythread8639 8643 pts/0 00:00:00 mythread8639 8644 pts/0 00:00:00 mythread
------------------------------------PID LWP TTY TIME CMD8639 8639 pts/0 00:00:00 mythread8639 8640 pts/0 00:00:00 mythread8639 8641 pts/0 00:00:00 mythread8639 8642 pts/0 00:00:00 mythread8639 8643 pts/0 00:00:00 mythread8639 8644 pts/0 00:00:00 mythread
------------------------------------
...
...
结果如预期(打印错乱是因为不同线程对显示器资源进行打印所导致);
除主线程外创建了5
个线程;
参考代码
(供参考) CSDN - Dio夹心小面包 / Gitee - 半介莽夫
相关文章:

『 Linux 』线程池与 POSIX 线程的封装编码实现
文章目录 线程池概念线程池的编码实现线程池的测试参考代码 线程的封装使用测试封装后的线程参考代码 线程池概念 池化技术是一种资源管理方法,通过预先创建和管理一组资源以便在需要使用时快速分配这些资源; 线程池是池化技术的一种典型应用; 资源分配 在线程池中预先创建一定…...

【C++】————哈希表
作者主页: 作者主页 本篇博客专栏:C 创作时间 :2024年8月6日 前言: 在计算机科学的广袤世界中,数据结构犹如基石,支撑着各种高效算法的构建与运行。而哈希表(Hash Table)&#…...

前端学习AI历程
AI基本概念tensorflow入门conda搭建环境,pycham使用训练自己的第一个模型AI目前前端方便入手的几个方向 素材图片库图像识别,在线学习低代码应用智能客服 获取数据集 roboflowkagglecocomakesense(用于打标) 认识yolo两个简单小应…...

常见中间件漏洞复现之【Tomcat】!
Tomcat介绍 tomcat是⼀个开源⽽且免费的jsp服务器,默认端⼝ : 8080,属于轻量级应⽤服务器。它可以实现 JavaWeb程序的装载,是配置JSP(Java Server Page)和JAVA系统必备的⼀款环境。 在历史上也披露出来了很多的漏洞 …...

C++并发编程(一):线程基础
简介 本文学习的是 b 站 up 恋恋风辰的并发编程教学视频做的一些笔记补充。 教程视频链接如下:线程基础:视频教程 文档链接如下:线程基础:笔记文档 理论上直接看 up 提供的笔记文档即可,我这里主要是记录一些我自己…...

enq: HW - contention事件来啦
业务系统反应数据库慢,根据时间查看awr报告。 先看一眼事件名称 HW enqueue 用于序列化超出段高水位线的空间分配。如果同时向对象添加大量数据,则多个进程可能同时尝试在高水位线上方分配空间,从而导致争用。 既然是控制资源并发的enq&…...

MyBatis补充
控制类和dao层接口以及mapper中的xml是怎样的关联的? 在Mybatis中,控制类和dao层接口是通过mapper的xml文件进行连接的。 控制类调用dao层接口中的方法,通过接口实现进行访问数据库操作。dao层接口定义数据库操作的方法,提供给控制…...

系统架构师(每日一练16)
每日一练 答案与解析 1.软件测试一般分为两个大类:动态测试和静态测试。前者通过运行程序发现错误,包括()等方法;后者采用人工和计算机辅助静态分析的手段对程序进行检测,包括()等方法。答案与解析 问题1 A.边界值分析、逻辑覆盖、基本路径 B.桌面检查、…...

实践致知第17享:电脑忽然黑屏的常见原因及处理方法
一、背景需求 小姑电话说:最近,电脑忽然就黑屏了(如下图所示),但是等待几十秒甚至一分钟,电脑就能自然恢复了,这种状况一天能出现三四次,怎么办? 二、分析诊断 电脑黑屏…...

微信小程序--实现地图定位---获取经纬度
(1) (2) (3) html: <view class"titleTwo" style"border: none;"><view class"fontSize30 invoiceTile">企业地址</view><view class"invoiceRight" bind:tap"tapChooseAddress" data-maptype"…...

【Python系列】使用 `isinstance()` 替代 `type()` 函数
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

【多模态大模型】 BLIP-2 in ICML 2023
一、引言 论文: BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 作者: Salesforce Research 代码: BLIP-2 特点: 该方法分别使用冻结的图像编码器(ViT-L/…...

HPC高性能计算平台
随着技术的发展和数据量的爆炸性增长,企业面临的挑战日益复杂,对计算能力的需求也在不断增加。这些问题的解决超出了传统计算方法的能力范围,高性能计算(HPC)正是为解决这类问题而生。 高性能计算(HPC&…...

前端常用的几个工具网站
觉得不错的前端工具类网站 1、Grid布局生成 https://cssgrid-generator.netlify.app 2、拟物按钮样式生成 https://neumorphism.io 3、玻璃形态效果 在线制作CSS玻璃形态 4、一些Button、checkBox、switch、card的css样式 零代码 - 精美CSS样式库 5、CSS阴影生成 在线创建…...

支付功能之代收代付
有很多老板问小编:“这个分账功能好是好,也能搞定项目中的二清问题和税务纠纷,但还是太复杂了,每次要添加被分账对象都需要提交材料进行审核,太繁琐了,有没有更方便快捷的支付产品来解决资金问题࿱…...

QPixmap
pixel[ˈpɪksl]像素 QPixmap 是 Qt 框架中用于处理图像的一个类。它主要用于在屏幕上显示和处理图像,提供了许多实用的功能,如加载、保存、缩放、旋转、合并等。 绘制 从文件加载:从指定文件加载图像。 QPixmap pixmap(":/images/exam…...

Laravel门面之下:构建自定义门面应用的艺术
Laravel门面之下:构建自定义门面应用的艺术 在Laravel框架中,门面(Facade)提供了一种将类静态调用与面向对象代码解耦的优雅方式。门面是一个全局可访问的类,它为底层复杂的服务提供了一个简单的接口。然而࿰…...

智启万象 | 2024 Google 开发者大会直播攻略
8 月 7 日上午 9:30 2024 Google 开发者大会 主旨演讲直播将准时开启 想要在线上探索大会精彩内容? 快查收这份观看指南! 8 月 7 日上午 9:30 2024 Google 开发者大会开幕 锁定大会官网观看主旨演讲现场直播! 本次大会内容将同步于多个…...

技巧:print打印内容到控制台时信息显示不全
# 请求一个接口,res是响应内容,使用res.text打印的信息不全 #使用流式处理响应 #如果你需要流式处理大的响应,确保你在处理响应内容的同时不会提前结束流。resself.request_base(select_api,change_datachange_data)print("")# pri…...

3.表的操作
目录 创建表 创建表案例: 查看表结构 修改表 1.增加新列 2.修改列的属性 3.删除列 4.修改表名 5.修改列 删除表 创建表 语法: CREATE TABLE [IF NOT EXISTS] table_name(field1 datatype1 [COMMENT 注释信息],field2 datatype2 [COMMENT 注释…...

AI回答:C#项目编译后生成部分文件的主要职责
【引入】以ConsoleApp1为例,请问C#编译之后以下文件有啥用 1.bin\runtimes 文件夹存放什么,有什么用? bin\runtimes 文件夹存放了项目的运行时相关文件,这些文件包括了各种目标平台的运行时库。 2.bin\生成的exe文件可以在别的电脑…...

RPC通信的简单流程
远程调用者假设需要调用Login方法,将调用的信息通过muduo库,同时进行了序列化和反序列化,发送到Rpcprovider上,RpcProvider通过对象和方法表来确定需要调用哪个服务对象的哪个方法。 UserRpcServiceRpc和UseRpcServiceRpcStub是继…...

前端发版(发包)缓存,需要强制刷新问题处理
问题原因: 浏览器问题 一、创建初始版本文件(public/version.json) { "version": "1722240835844" }二、设置版本判断(version.js) import axios from "axios";const isNewVersion () > {let baseUrl …...

洛谷练习(8.4/8.5)
题目 P2036 PERKET题目描述思路代码 P3799 小 Y 拼木棒题目描述思路代码 P1010 幂次方题目描述思路代码 P1498 南蛮图腾题目描述思路代码 P1928 外星密码题目描述思路代码 P2036 PERKET 题目描述 比较苦度和酸度的最小差值 思路 搜索最小差值 代码 void dfs(int sd,int k…...

DLMS/COSEM中的信息安全:加密算法(下)1
4.公钥算法 4.1概述 一般来说,公钥密码系统使用难以解决的问题作为算法的基础。RSA算法基于非常大的整数的素因子分解。椭圆曲线密码学(ECC)是基于求解椭圆曲线离散对数问题(ECDLP)的难度。与RSA相比,ECC提供了相似的安全级别,但密钥大小明显减少。ECC特别适用于嵌入式…...

ES6中的Promise、async、await,超详细讲解!
Promise是es6引入的异步编程新解决方案,Promise实例和原型上有reject、resolve、all、then、catch、finally等多个方法,语法上promise就是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果,本篇文章主要介绍了ES6中的P…...

Modbus poll和Modbus Mbslave的使用
读取Modbus Mbslave中的数据 首先创建COM1和COM2端口 然后 using System.IO.Ports; namespace 通信 {internal class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");SerialPort serialPort new SerialPort("COM1",960…...

树莓集团的全球化征程:数字媒体产业的本土与国际布局
在全球数字化转型的浪潮中,树莓集团正稳步推进数字媒体产业从本土到国际的全球化布局。在数字媒体产业这一新兴且充满活力的领域中,树莓集团不仅在国内市场树立了标杆,更以其独特的全球化战略布局,引领着行业的未来趋势。 本土深耕…...

LeetCode面试150——274H指数
题目难度:中等 默认优化目标:最小化平均时间复杂度。 Python默认为Python3。 目录 1 题目描述 2 题目解析 3 算法原理及代码实现 3.1 排序 3.2 排序时间优化(计数排序) 3.3 二分查找 参考文献 1 题目描述 给你一个整数数组 citations …...

【Linux】Linux重定向指南:探索输出重定向与追加重定向的奥秘!
欢迎来到 CILMY23 的博客 🏆本篇主题为:Linux重定向指南:探索输出重定向与追加重定向的奥秘! 🏆个人主页:CILMY23-CSDN博客 🏆系列专栏:Python | C | C语言 | 数据结构与算法 | 贪…...