【Linux】使用管道实现一个简易版本的进程池
文章目录
- 使用管道实现一个简易版本的进程池
- 流程图
- 代码
- makefile
- Task.hpp
- ProcessPool.cc
- 程序流程:
使用管道实现一个简易版本的进程池
流程图
代码
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)
,将task1
到task4
四个任务的函数地址存入全局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
?
文件描述符的继承:
当父进程通过
fork()
创建子进程时,子进程会继承父进程的所有打开的文件描述符。如果父进程创建了多个管道(每个子进程对应一个管道),那么每个子进程都会继承所有管道的文件描述符,即使这些管道是用于其他子进程的。
资源泄露问题:
如果子进程不关闭不需要的文件描述符,这些文件描述符会一直保持打开状态,导致资源泄露。
例如,假设父进程创建了 10 个子进程,每个子进程都会继承 10 个管道的文件描述符,但实际上每个子进程只需要一个管道的读端文件描述符。
避免干扰:
- 如果子进程不关闭不需要的文件描述符,可能会导致意外的行为。例如,某个子进程可能会错误地读取其他子进程的管道数据。
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 程序流程: 使用管道实现一个简易版本的进程池 流程图 代码 makefile ProcessPool:ProcessPool.ccg -o $ $^ -g -stdc11 .PHONY:clean clean:rm -f ProcessPoolTask.hpp #pr…...

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

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

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

FPGA|使用quartus II通过AS下载POF固件
1、将开发板设置到AS下载挡位,或者把下载线插入到AS端口 2、打开quartus II,选择Tools→Programmer→ Mode选择Active Serial Programming 3、点击左侧Add file…,选择 .pof 文件 →start 4、勾选program和verify(可选࿰…...

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

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

【LeetCode: 958. 二叉树的完全性检验 + bfs + 二叉树】
🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…...

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上 如何绘制图像 如何绘制文字 如何获取字体? 如何正确的访问字体 如何抽象字体 如何绘制字符串 绘制方案 文本绘制 更加方便的绘制 字体附录 ascii 6x8字体 ascii 8 x 16字体 基础组件实现 我们现在离手…...

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

VS安卓仿真器下载失败怎么办?
如果网络不稳定,则VS的安卓仿真器很容易下载失败,如下 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可靠传输,流量控制 可靠传输:每字节对应一个序号 累计确认:收到ack则正确接收 返回ack推迟确认(不超过0.5s) 两种ack:专门确认(只有首部无数据) 捎带确认(带数据…...

视频拼接,拼接时长版本
目录 视频较长,分辨率较大,这个效果很好,不耗用内存 ffmpeg imageio,适合视频较短 视频较长,分辨率较大,这个效果很好,不耗用内存 ffmpeg import subprocess import glob import os from nats…...

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

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

LLMs之WebRAG:STORM/Co-STORM的简介、安装和使用方法、案例应用之详细攻略
LLMs之WebRAG: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中,PageTransitionEnter和PageTransitionExit是用于控制页面切换动画的参数。它们分别表示页面进入和退出时的动画。1. type(动画类型…...

图漾相机-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.小程序管理的概念,以及成员管理两个方面 3.开发者权限说明以及如何维护项目成员 4.小程序版本...

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

计算机网络一点事(20)
IEEE802.11 无线局域网 分类有无基础设施 星型拓扑,基本服务集BSS一基站多移动站,服务集标识符SSID不超过32b,可接入802.3 漫游:移动站从一个基本服务集切换到另一个(类似换联WiFi) 802.11帧࿱…...

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安装包,地址:Node.js — Download Node.js 先在node.js即将要安装的路径下创建两个文件夹:node_cache(缓存)、node_global(全局) 点击安装包…...

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

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

深度学习编译器的演进:从计算图到跨硬件部署的自动化之路
第一章 问题的诞生——深度学习部署的硬件困境 1.1 计算图的理想化抽象 什么是计算图? 想象你正在组装乐高积木。每个积木块代表一个数学运算(如加法、乘法),积木之间的连接代表数据流动。深度学习框架正是用这种"积木拼接…...

【数据结构】_顺序表经典算法OJ(力扣版)
目录 1. 移除元素 1.1 题目描述及链接 1.2 解题思路 1.3 程序 2. 合并两个有序数组 1.1 原题链接及题目描述 1.2 解题思路 1.3 程序 1. 移除元素 1.1 题目描述及链接 原题链接:27. 移除元素 - 力扣(LeetCode) 题目描述:…...

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

第05章 17 Contour 过滤器介绍与例子
vtkContourFilter 是 VTK(Visualization Toolkit)中的一个关键类,用于从输入数据生成等值线或等值面。它是基于阈值的过滤器,可以从标量字段中提取等值线或等值面。vtkContourFilter 的核心功能是根据用户指定的值生成等值线或等值…...