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

【Linux】使用管道实现一个简易版本的进程池

文章目录

  • 使用管道实现一个简易版本的进程池
    • 流程图
    • 代码
      • makefile
      • Task.hpp
      • ProcessPool.cc
    • 程序流程:


使用管道实现一个简易版本的进程池

流程图

bd6b8d79d30bd281ae2d181d5a7fa689


代码

makefile

ProcessPool:ProcessPool.ccg++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:rm -f ProcessPool

Task.hpp

#pragma once#include <iostream>
#include <vector>typedef void (*task_t)(); //定义了一个函数指针类型task_t,它指向返回类型为void且不接受任何参数的函数。void task1()
{std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{std::cout << "lol 用户释放技能,更新用的血量和蓝量" << std::endl;
}void LoadTask(std::vector<task_t> *tasks) // 该函数接受一个指向std::vector<task_t>的指针,并将其作为参数
{tasks->push_back(task1); //将task1函数的地址添加到向量中。tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}

ProcessPool.cc

#include "Task.hpp"  // 包含任务相关的头文件
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>const int processnum = 10;  // 设定进程池大小为10
std::vector<task_t> tasks; // 存储任务的向量// 定义channel类,用于管理进程间通信
class channel
{
public:channel(int cmdfd, int slaverid, const std::string &processname):_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname){}
public:int _cmdfd;               // 用于向子进程发送命令的文件描述符pid_t _slaverid;          // 子进程IDstd::string _processname; // 子进程名称,用于日志显示
};// 子进程执行的函数
void slaver()
{while(true){int cmdcode = 0;// 从标准输入(被重定向到管道)读取命令int n = read(0, &cmdcode, sizeof(int)); if(n == sizeof(int)){std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;// 执行对应的任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break; // 管道关闭时退出}
}// 初始化进程池
void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds;  // 存储历史文件描述符for(int i = 0; i < processnum; i++){int pipefd[2];int n = pipe(pipefd);  // 创建管道assert(!n);(void)n;pid_t id = fork();     // 创建子进程if(id == 0) // 子进程{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " "; // 打印当前文件描述符的值,用于显示子进程正在关闭哪些文件描述符。close(fd); // 关闭文件描述符}std::cout << "\n";close(pipefd[1]);  // 关闭写端dup2(pipefd[0], 0);  // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver();          // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);}// 父进程close(pipefd[0]);  // 关闭读端// 创建新的channel并添加到channels中std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfds.push_back(pipefd[1]);sleep(1);}
}// 打印调试信息
void Debug(const std::vector<channel> &channels)
{for(const auto &c :channels){std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;}
}// 显示菜单
void Menu()
{std::cout << "################################################" << std::endl;std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;std::cout << "#                         0. 退出               #" << std::endl;std::cout << "#################################################" << std::endl;
}// 控制子进程执行任务
void ctrlSlaver(const std::vector<channel> &channels)
{int which = 0;while(true){int select = 0;Menu();std::cout << "Please Enter@ ";std::cin >> select;if(select <= 0 || select >= 5) break;int cmdcode = select - 1;// 轮询方式分配任务给子进程std::cout << "father say: " << " cmdcode: " <<cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));which++;which %= channels.size();}
}// 清理进程池
void QuitProcess(const std::vector<channel> &channels)
{for(const auto &c : channels){close(c._cmdfd);  // 关闭所有管道waitpid(c._slaverid, nullptr, 0);  // 等待所有子进程结束}
}int main()
{LoadTask(&tasks);  // 加载任务列表srand(time(nullptr)^getpid()^1023);  // 初始化随机数种子std::vector<channel> channels; //InitProcessPool(&channels);  // 初始化进程池ctrlSlaver(channels);  // 控制子进程执行任务QuitProcess(channels);  // 清理进程池return 0;
}

程序流程:

1.main函数首先调用LoadTask(&tasks),将task1task4四个任务的函数地址存入全局tasks向量。

2.srand(time(nullptr)^getpid()^1023); 初始化随机数种子

3.std::vector<channel> channels;,这行代码的作用是定义一个名为 channels 的向量(std::vector),用于存储 channel 类型的对象。它的主要作用是管理多个 channel 对象,每个 channel 对象代表一个子进程的通信通道。

  • 每个 channel 对象包含以下信息:

    • _cmdfd:用于向子进程发送命令的文件描述符(管道写端)。

    • _slaverid:子进程的进程ID(PID)。

    • _processname:子进程的名称,用于日志和调试。

  • channels 向量存储了所有子进程的通信信息,父进程可以通过它管理所有子进程。

4.InitProcessPool(&channels); ,初始化进程池

// 初始化进程池
void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds;  // 存储历史文件描述符for(int i = 0; i < processnum; i++){int pipefd[2];int n = pipe(pipefd);  // 创建管道assert(!n);(void)n;pid_t id = fork();     // 创建子进程if(id == 0) // 子进程{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " ";close(fd);}std::cout << "\n";close(pipefd[1]);  // 关闭写端dup2(pipefd[0], 0);  // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver();          // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);}// 父进程close(pipefd[0]);  // 关闭读端// 创建新的channel并添加到channels中std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfds.push_back(pipefd[1]);sleep(1);}
}

5.std::vector<int> oldfds; 的作用是存储父进程中已经创建的管道的写端文件描述符(pipefd[1])。它的主要目的是在创建新的子进程时,确保子进程能够关闭不需要的文件描述符,避免资源泄露和潜在的问题。

为什么需要 oldfds

  1. 文件描述符的继承

    • 当父进程通过 fork() 创建子进程时,子进程会继承父进程的所有打开的文件描述符。

    • 如果父进程创建了多个管道(每个子进程对应一个管道),那么每个子进程都会继承所有管道的文件描述符,即使这些管道是用于其他子进程的。

  2. 资源泄露问题

    • 如果子进程不关闭不需要的文件描述符,这些文件描述符会一直保持打开状态,导致资源泄露。

    • 例如,假设父进程创建了 10 个子进程,每个子进程都会继承 10 个管道的文件描述符,但实际上每个子进程只需要一个管道的读端文件描述符。

  3. 避免干扰

    • 如果子进程不关闭不需要的文件描述符,可能会导致意外的行为。例如,某个子进程可能会错误地读取其他子进程的管道数据。

6.for(int i = 0; i < processnum; i++),循环 processnum=10 次,每次创建一个子进程和一个管道。

7.int pipefd[2];

pipefd 是一个长度为 2 的整型数组,用于存储管道的两个文件描述符:

  • pipefd[0]:管道的 读端文件描述符,用于从管道中读取数据。
  • pipefd[1]:管道的 写端文件描述符,用于向管道中写入数据。

8.int n = pipe(pipefd);

调用 pipe 系统函数来创建一个管道,并将结果存储在变量 n 中。

1. pipe 系统函数的作用

pipe 是一个系统调用,用于创建一个管道。管道的本质是一个内核缓冲区,用于在两个进程之间传递数据。管道有两个端点:

  • 读端:用于从管道中读取数据。
  • 写端:用于向管道中写入数据。

pipe 函数的原型如下:

int pipe(int pipefd[2]);

2. 参数 pipefd[2]

  • pipefd 是一个长度为 2 的整型数组,用于存储管道的两个文件描述符:
    • pipefd[0]:管道的 读端文件描述符,用于从管道中读取数据。
    • pipefd[1]:管道的 写端文件描述符,用于向管道中写入数据。

3. 返回值 n

  • 如果 pipe 调用成功,返回 0
  • 如果 pipe 调用失败,返回 -1,并设置 errno 表示错误原因。

4. 代码解析

int n = pipe(pipefd);
  • pipe(pipefd):调用 pipe 函数创建管道。
  • n:存储 pipe 函数的返回值,用于检查管道是否创建成功。

9.assert(!n);(void)n;

  • assert(!n):确保管道创建成功。如果 pipe 调用失败,程序会终止。
  • (void)n:忽略未使用的变量警告。

10.pid_t id = fork(); ,创建子进程

if(id == 0) // 子进程
{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " ";close(fd);}std::cout << "\n";close(pipefd[1]);  // 关闭写端dup2(pipefd[0], 0);  // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver();          // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);
}

11.在子进程中,id 为 0。

12.std::cout << "child: " << getpid() << " close history fd: ";

打印当前子进程的PID,用于区分不同子进程

" close history fd: ",说明接下来要关闭的文件描述符

for(auto fd : oldfds) {std::cout << fd << " ";// 打印当前文件描述符的值,用于显示子进程正在关闭哪些文件描述符。close(fd);// 关闭文件描述符
}

在子进程中遍历 oldfds 向量,关闭所有不需要的文件描述符。

具体来说,它的目的是确保子进程只保留与自己相关的文件描述符,关闭其他无关的文件描述符,从而避免资源泄露和潜在的问题。

close(pipefd[1]);  // 子进程关闭写端,因为子进程只需要读取命令
dup2(pipefd[0], 0);  // 将父进程管道读端重定向到标准输入
close(pipefd[0]); //关闭父进程读端
slaver();          // 执行子进程任务

dup2函数将管道的读端(pipefd[0])复制到标准输入(0)

这意味着之后从标准输入读取的数据实际上是从管道读取的

后续代码中可以直接使用read(0,…)来读取父进程发送的数据

数据流向:父进程 ---> 写端(pipefd[1]) ---> 管道 ---> 读端(重定向到标准输入) ---> 子进程

子进程:

  • 关闭写端(pipefd[1])
  • 将读端重定向到标准输入
  • 关闭原读端(因为已重定向)

15.进入子进程函数

// 子进程执行的函数
void slaver()
{while(true){int cmdcode = 0;// 从标准输入(被重定向到管道)读取命令int n = read(0, &cmdcode, sizeof(int)); if(n == sizeof(int)){std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;// 执行对应的任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break; // 管道关闭时退出}
}

while(true),无限循环,持续监听命令

int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); 

read(0, …):从标准输入读取数据,因为前面做了重定向,实际是从管道读取

&cmdcode:存储读取数据的地址

sizeof(int):读取int大小的数据

n:返回实际读取的字节数

if(n == sizeof(int)) {  // 成功读取到完整的命令// 打印调试信息std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;// 执行对应任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();  // 调用任务函数
}

if(cmdcode >= 0 && cmdcode < tasks.size()),确保cmdcode非负,确保cmdcode小于任务数组大小,防止数组越界访问

tasks[cmdcode]();tasks[cmdcode]获取对应的函数指针,()操作符调用该函数。

// 假设cmdcode = 0
tasks[0](); // 调用task1(),输出"lol 刷新日志"// 假设cmdcode = 1
tasks[1](); // 调用task2(),输出"lol 更新野区,刷新出来野怪"// 假设cmdcode = 2
tasks[2](); // 调用task3(),输出"lol 检测软件是否更新"// 假设cmdcode = 3
tasks[3](); // 调用task4(),输出"lol 更新用户血量和蓝量"

if(n == 0) break; ,管道关闭时退出

16.slaver()结束,返回刚刚的

std::cout << "process : " << getpid() << " quit" << std::endl; //打印退出信息,getpid帮助我们确认哪个进程正在退出
exit(0); // 立即终止当前进程

17.然后执行InitProcessPool()函数的剩下来部分

// 父进程
close(pipefd[0]);  // 关闭读端// 创建新的channel并添加到channels中
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
oldfds.push_back(pipefd[1]);sleep(1);

close(pipefd[0]);,父进程只需写入命令,不需要读。及时关闭不需要的文件描述符

std::string name = "process-" + std::to_string(i);,为每个子进程创建唯一名称。

std::to_string(i) : 将数字i转为字符串,“+” : 字符串拼接运算符。

效果如:process-0, process-1, process-2…

channels->push_back(channel(pipefd[1], id, name));push_back在容器末尾添加新元素。创建临时 channel 对象并添加到 vector

channel是一个结构体,存储子进程信息:

void InitProcessPool(std::vector<channel> *channels)struct channel {int fd;      // 管道写端pid_t pid;   // 子进程IDstd::string name;  // 进程名称channel(int _fd, pid_t _pid, const std::string& _name): fd(_fd), pid(_pid), name(_name){}
};

oldfds.push_back(pipefd[1]);,添加管道写端的文件描述符。

保存文件描述符的用途:

  • 用于后续关闭文件描述符
  • 防止文件描述符泄漏
  • 进程间通信的管理
  • 资源清理

sleep(1);,休眠1s。

18.进入main函数,执行ctrlSlaver(channels);

// 控制子进程执行任务
void ctrlSlaver(const std::vector<channel> &channels)
{int which = 0;while(true){int select = 0;Menu();std::cout << "Please Enter@ ";std::cin >> select;if(select <= 0 || select >= 5) break;int cmdcode = select - 1;// 轮询方式分配任务给子进程std::cout << "father say: " << " cmdcode: " <<cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));which++;which %= channels.size();}
}

轮询机制

int which = 0;  // 轮询索引
which++;
which %= channels.size();  // 循环轮询

实现了循环分配任务给不同子进程

如果有3个进程,which的值会是 0,1,2,0,1,2…


任务选择

while(true) {int select = 0;Menu();  // 显示菜单std::cout << "Please Enter@ ";std::cin >> select;  // 获取用户输入if(select <= 0 || select >= 5) break;  // 退出条件int cmdcode = select - 1;  // 将用户输入的选项编号转换为程序内部使用的命令代码。
}

发送任务示例

// 显示任务分配信息
std::cout << "father say: " << " cmdcode: " << cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;// 向子进程发送命令
write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));

cmdcode要执行的命令编号(0代表hello,1代表calc等)

_slaverid: 子进程的PID(进程ID)

_processname: 子进程的名称

write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));

channels[which]._cmdfd:管道的写端文件描述符

&cmdcode:命令代码的地址

sizeof(cmdcode):发送的字节数(int类型通常是4字节)

19.返回主函数,执行QuitProcess(channels);,清理进程池。

void QuitProcess(const std::vector<channel> &channels)
{// 遍历所有channel对象for(const auto &c : channels){// 1. 关闭管道close(c._cmdfd);  // 关闭管道写端// 2. 等待子进程结束waitpid(c._slaverid, nullptr, 0);  // 阻塞等待直到子进程结束}
}

20.return 0;

相关文章:

【Linux】使用管道实现一个简易版本的进程池

文章目录 使用管道实现一个简易版本的进程池流程图代码makefileTask.hppProcessPool.cc 程序流程&#xff1a; 使用管道实现一个简易版本的进程池 流程图 代码 makefile ProcessPool:ProcessPool.ccg -o $ $^ -g -stdc11 .PHONY:clean clean:rm -f ProcessPoolTask.hpp #pr…...

【OpenGL】OpenGL游戏案例(二)

文章目录 特殊效果数据结构生成逻辑更新逻辑 文本渲染类结构构造函数加载函数渲染函数 特殊效果 为提高游戏的趣味性&#xff0c;在游戏中提供了六种特殊效果。 数据结构 PowerUp 类只存储存活数据&#xff0c;实际逻辑在游戏代码中通过Type字段来区分执行 class PowerUp …...

28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正

这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能&#xff1a;报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...

Java 泛型<? extends Object>

在 Java 泛型中&#xff0c;<? extends Object> 和 <?> 都表示未知类型&#xff0c;但它们在某些情况下有细微的差异。泛型的引入是为了消除运行时错误并增强类型安全性&#xff0c;使代码更具可读性和可维护性。 在 JDK 5 中引入了泛型&#xff0c;以消除编译时…...

FPGA|使用quartus II通过AS下载POF固件

1、将开发板设置到AS下载挡位&#xff0c;或者把下载线插入到AS端口 2、打开quartus II&#xff0c;选择Tools→Programmer→ Mode选择Active Serial Programming 3、点击左侧Add file…&#xff0c;选择 .pof 文件 →start 4、勾选program和verify&#xff08;可选&#xff0…...

“新月之智”智能战术头盔系统(CITHS)

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章链接&#xff08;更新&#xff09;&#xff1a; 星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 目录 一、引言 二、智能头盔控制系统概述 三、系统架…...

php:代码中怎么搭建一个类似linux系统的crontab服务

一、前言 最近使用自己搭建的php框架写一些东西&#xff0c;需要用到异步脚本任务的执行&#xff0c;但是是因为自己搭建的框架没有现成的机制&#xff0c;所以想自己搭建一个类似linux系统的crontab服务的功能。 因为如果直接使用linux crontab的服务配置起来很麻烦&#xff0…...

【LeetCode: 958. 二叉树的完全性检验 + bfs + 二叉树】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…...

MinDoc 安装与部署

下载可执行文件 mindoc mindoc_linux_amd64.zip 上传并解压压缩包 cd /opt mkdir mindoc cd mindocunzip mindoc_linux_amd64.zip 创建数据库 CREATE DATABASE mindoc_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; 配置数据库 将解压目录下 conf/app.conf.exam…...

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础组件实现)

目录 基础组件实现 如何将图像和文字显示到OLED上 如何绘制图像 如何绘制文字 如何获取字体&#xff1f; 如何正确的访问字体 如何抽象字体 如何绘制字符串 绘制方案 文本绘制 更加方便的绘制 字体附录 ascii 6x8字体 ascii 8 x 16字体 基础组件实现 我们现在离手…...

windows系统如何检查是否开启了mongodb服务

windows系统如何检查是否开启了mongodb服务&#xff01;我们有很多软件开发&#xff0c;网站开发时候需要使用到这个mongodb数据库&#xff0c;下面我们看看&#xff0c;如何在windows系统内排查&#xff0c;是否已经启动了本地服务。 在 Windows 系统上&#xff0c;您可以通过…...

VS安卓仿真器下载失败怎么办?

如果网络不稳定&#xff0c;则VS的安卓仿真器很容易下载失败&#xff0c;如下 Downloaded file <USER_HOME>\AppData\Local\Temp\xamarin-android-sdk\x86_64-35_r08.zip not found for Android SDK archive https://dl.google.com/android/repository/sys-img/google_a…...

计算机网络一点事(24)

TCP可靠传输&#xff0c;流量控制 可靠传输&#xff1a;每字节对应一个序号 累计确认&#xff1a;收到ack则正确接收 返回ack推迟确认&#xff08;不超过0.5s&#xff09; 两种ack&#xff1a;专门确认&#xff08;只有首部无数据&#xff09; 捎带确认&#xff08;带数据…...

视频拼接,拼接时长版本

目录 视频较长&#xff0c;分辨率较大&#xff0c;这个效果很好&#xff0c;不耗用内存 ffmpeg imageio&#xff0c;适合视频较短 视频较长&#xff0c;分辨率较大&#xff0c;这个效果很好&#xff0c;不耗用内存 ffmpeg import subprocess import glob import os from nats…...

制造企业的成本核算

一、生产成本与制造费用的区别 (1)生产成本,是直接用于产品生产,构成产品实体的材料成本。 包括企业在生产经营过程中实际消耗的原材料、辅助材料、备品备件、外购半成品、燃料、动力包装物以及其它直接材料,和直接参加产品生产的工人工资,以及按生产工人的工资总额和规…...

doris:高并发导入优化(Group Commit)

在高频小批量写入场景下&#xff0c;传统的导入方式存在以下问题&#xff1a; 每个导入都会创建一个独立的事务&#xff0c;都需要经过 FE 解析 SQL 和生成执行计划&#xff0c;影响整体性能每个导入都会生成一个新的版本&#xff0c;导致版本数快速增长&#xff0c;增加了后台…...

LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略

LLMs之WebRAG&#xff1a;STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略 目录 STORM系统简介 1、Co-STORM 2、更新新闻 STORM系统安装和使用方法 1、安装 pip安装 直接克隆GitHub仓库 2、模型和数据集 两个数据集 FreshWiki数据集 WildSeek数据集 支持…...

鸿蒙HarmonyOS实战-ArkUI动画(页面转场动画)_鸿蒙arkui tab 切换动画

PageTransitionExit({type?: RouteType,duration?: number,curve?: Curve | string,delay?: number}) 在HarmonyOS中&#xff0c;PageTransitionEnter和PageTransitionExit是用于控制页面切换动画的参数。它们分别表示页面进入和退出时的动画。1. type&#xff08;动画类型…...

图漾相机-ROS2-SDK-Ubuntu版本编译(新版本)

文章目录 前言1.Camport ROS2 SDK 介绍1.1 Camport ROS2 SDK源文件介绍1.2 Camport ROS2 SDK工作流程1.2.1 包含头文件1.2.2 2 初始化 ROS 2 节点1.2.3 创建节点对象1.2.4 创建发布者对象并实现发布逻辑1.2.5 启动 ROS 2 1.3 ROS2 SDK环境配置与编译1.3.1 Ubuntu 20.04 下ROS2 …...

小程序的协同工作与发布

1.小程序API的三大分类 2.小程序管理的概念&#xff0c;以及成员管理两个方面 3.开发者权限说明以及如何维护项目成员 4.小程序版本...

解锁维特比算法:探寻复杂系统的最优解密码

引言 在复杂的技术世界中&#xff0c;维特比算法以其独特的魅力和广泛的应用&#xff0c;成为通信、自然语言处理、生物信息学等领域的关键技术。今天&#xff0c;让我们一同深入探索维特比算法的奥秘。 一、维特比算法的诞生背景 维特比算法由安德鲁・维特比在 1967 年提出…...

计算机网络一点事(20)

IEEE802.11 无线局域网 分类有无基础设施 星型拓扑&#xff0c;基本服务集BSS一基站多移动站&#xff0c;服务集标识符SSID不超过32b&#xff0c;可接入802.3 漫游&#xff1a;移动站从一个基本服务集切换到另一个&#xff08;类似换联WiFi&#xff09; 802.11帧&#xff1…...

java求职学习day23

MySQL 单表 & 约束 & 事务 1. DQL操作单表 1.1 创建数据库,复制表 1) 创建一个新的数据库 db2 CREATE DATABASE db2 CHARACTER SET utf8; 2) 将 db1 数据库中的 emp 表 复制到当前 db2 数据库 1.2 排序 通过 ORDER BY 子句 , 可以将查询出的结果进行排序 ( 排序只…...

Vue-cli 脚手架搭建

安装node.js 官网下载node.js安装包&#xff0c;地址&#xff1a;Node.js — Download Node.js 先在node.js即将要安装的路径下创建两个文件夹&#xff1a;node_cache&#xff08;缓存&#xff09;、node_global&#xff08;全局&#xff09; 点击安装包&#xf…...

认识小程序的基本组成结构

1.基本组成结构 2.页面的组成部分 3.json配置文件 4.app.json文件(全局配置文件&#xff09; 5.project.config.json文件 6.sitemap.json文件 7.页面的.json配置文件 通过window节点可以控制小程序的外观...

Spring Boot 热部署实现指南

在开发 Spring Bot 项目时&#xff0c;热部署功能能够显著提升开发效率&#xff0c;让开发者无需频繁重启服务器就能看到代码修改后的效果。下面为大家详细介绍一种实现 Spring Boot 热部署的方法&#xff0c;同时也欢迎大家补充其他实现形式。 步骤一、开启 IDEA 自动编译功能…...

深度学习编译器的演进:从计算图到跨硬件部署的自动化之路

第一章 问题的诞生——深度学习部署的硬件困境 1.1 计算图的理想化抽象 什么是计算图&#xff1f; 想象你正在组装乐高积木。每个积木块代表一个数学运算&#xff08;如加法、乘法&#xff09;&#xff0c;积木之间的连接代表数据流动。深度学习框架正是用这种"积木拼接…...

【数据结构】_顺序表经典算法OJ(力扣版)

目录 1. 移除元素 1.1 题目描述及链接 1.2 解题思路 1.3 程序 2. 合并两个有序数组 1.1 原题链接及题目描述 1.2 解题思路 1.3 程序 1. 移除元素 1.1 题目描述及链接 原题链接&#xff1a;27. 移除元素 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a…...

数据结构:队列篇

图均为手绘,代码基于vs2022实现 系列文章目录 数据结构初探: 顺序表 数据结构初探:链表之单链表篇 数据结构初探:链表之双向链表篇 链表特别篇:链表经典算法问题 数据结构:栈篇 文章目录 系列文章目录前言一.队列的概念和结构1.1概念一、动态内存管理优势二、操作效率与安全性…...

第05章 17 Contour 过滤器介绍与例子

vtkContourFilter 是 VTK&#xff08;Visualization Toolkit&#xff09;中的一个关键类&#xff0c;用于从输入数据生成等值线或等值面。它是基于阈值的过滤器&#xff0c;可以从标量字段中提取等值线或等值面。vtkContourFilter 的核心功能是根据用户指定的值生成等值线或等值…...