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

Linux笔记---进程间通信:匿名管道

1. 管道通信

1.1 管道的概念与分类

管道(Pipe) 是进程间通信(IPC)的一种基础机制,主要用于在具有亲缘关系的进程(如父子进程、兄弟进程)之间传递数据,其核心特性是通过内核缓冲区实现单向或半双工的数据传输。

  • 匿名管道:通常用于具有亲缘关系的进程之间通信,如父子进程或兄弟进程。它是半双工的,数据只能在一个方向上流动,有固定的读端和写端,且只存在于内存中,不属于任何文件系统,但可以使用普通的read、write等函数进行读写。
  • 命名管道(FIFO):可以在无关的进程之间进行通信,有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中。创建后,无关进程可以通过该文件进行通信,通信方式类似于使用文件传输数据,遵循先进先出原则。

管道是轻量级且高效的进程间通信方式,适用于简单的数据流场景,但其单向性和容量限制使其不适合复杂需求。命名管道扩展了应用范围,但需注意文件系统的依赖。

1.2 管道的原理

在操作系统还不支持进程间通信的时候,人们尝试使用操作系统已有的功能来实现进程间通信。

要实现进程间通信,就需要两个进程访问共享的资源,什么资源是各个进程都可以共享访问的呢?

答案显而易见:文件。

父进程打开一个文件并创建子进程,子进程就会继承父进程的文件描述符表,这样父子进程就可以访问同一个文件,通过向文件当中进行读写就可以实现进程间通信。

当然,对文件的访问是需要同步与互斥机制的,这一点由操作系统来实现,我们并不关心。

两个进程之间的通信一般都是些临时的小体量的消息,无需将其正真存入到文件当中(而且存入文件当中会造成较大的访存消耗)。实际上,我们只需要在struct file维护的文件缓冲区当中进行信息交换即可。

于是,在操作系统在这个思路的基础之上,实现了管道机制。

所谓管道,就是一种特殊的管道文件,其本质上是内核管理的一段环形内存缓冲区,通过文件描述符提供单向或半双工的数据流传输。

2. 匿名管道的使用

2.1 pipe函数

我们说,管道文件是一种特殊的文件,那么其打开的方式(或者说创建的方式)自然也要与一般的文件进行区别。

在Linux当中,我们使用pipe函数来创建一个匿名管道:

#include <unistd.h>
int pipe(int pipefd[2]);

返回值:成功返回 0,失败返回 -1 并设置 errno。

参数:pipefd 是长度为 2 的整型数组,用于返回两个文件描述符:

  • pipefd[0]:管道的读端,只能用于读取数据。
  • pipefd[1]:管道的写端,只能用于写入数据。

注意,管道只能进行单向数据传输,这意味着共享管道的父子进程一个只能读,一个只能写。

在实践当中,我们应当关闭当前进程未使用的端口:

#include <unistd.h>int main()
{int pipefd[2] = {0};int n = pipe(pipefd);if(n == -1){perror("pipe:");return 1;}int id = fork();if(id == 0){// 子进程写close(fd[0]);// ...}else{// 父进程读close(fd[1]);// ...}
}

2.2 管道读写规则

当没有数据可读时:

  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read调用返回 -1,errno值为EAGAIN。

当管道满的时:

  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

文件描述符关闭:

  • 如果所有管道写端对应的文件描述符被关闭:read不再阻塞而是返回0。
  • 如果所有管道读端对应的文件描述符被关闭:write操作会产生信号SIGPIPE,进而可能导致write进程退出。

原子性规则: 

  • 小数据写入(≤ PIPE_BUF,通常 4KB): 内核保证写入的原子性,即数据要么完整写入,要么完全不写入。
  • 大数据写入(PIPE_BUF): 不保证原子性,数据可能被其他进程的写入操作穿插,且可能部分写入。

注:O_NONBLOCK为pipe2的选项(比pipe多一个选项参数)。 

 3. 进程池

学习完匿名管道的基本使用,我们可以动手尝试编写一个基于匿名管道的进程池。

平时,各个子进程就阻塞在read处等待,当父进程通过管道对其下达任务时就会将其唤醒。

.hpp后缀的文件其实就是.cpp和.h文件的结合体,类似于java的包。

3.1 Channel.hpp

首先,我们定义一个Channel类用于管理父子进程之间的通信管道(信道):

#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
#include <iostream>class Channel
{
public:Channel(int wfd, int pid):_wfd(wfd), _process(pid){}~Channel(){}void CloseAndWait(){close(_wfd);std::cout << _process << "的信道关闭成功" << std::endl;waitpid(_process, nullptr, 0);std::cout << "进程" << _process << "已被成功回收" << std::endl;}// 通过信道将任务提交给子进程执行void ExecuteTask(int code){std::cout << "将任务" << code << "派遣给" << _process << std::endl;write(_wfd, &code, sizeof(code));}int GetPid(){return _process;}
private:int _wfd;pid_t _process;
};

由于进程池中进程的数量可能很多,信道也相对变多,我们应当定义一个类来管理这些信道:

class ChannelManager
{
public:void Insert(Channel&& channel){_Channels.push_back(channel);}int Size(){return _Channels.size();}// 选择进程并将任务分派出去void GiveTask(int code){int channel = SelectChannel();std::cout << "选择进程: " << _Channels[channel].GetPid() << std::endl;_Channels[channel].ExecuteTask(code);}void CloseAndWait(){for(auto& channel : _Channels){channel.CloseAndWait();}}private:// 选择进程int SelectChannel(){// 轮询分派任务static int next = 0;assert(_Channels.size());int tmp = next;next = (next + 1) % _Channels.size();return tmp;}std::vector<Channel> _Channels;
};

3.2 Task.hpp

任务实际上就是一个个的函数,同样地,由于任务可能有很多,我们也使用一个类来进行管理:

#include <functional>
#include <vector>
#include <iostream>
#include <unistd.h>
#include <cassert>
using Task = std::function<void()>;class TaskManager
{
public:// 注册,即将任务插入数组并管理起来void RegisterTask(Task&& task){_Tasks.push_back(task);}int Size(){return _Tasks.size();}// 根据任务码(数组下标)返回相应的任务对象Task& GetTask(int code){assert(code >= 0 && code < _Tasks.size());return _Tasks[code];}
private:std::vector<Task> _Tasks;
};

3.3 ProcessPool.hpp

完成上面的准备工作,我们就可以开始着手构建我们ProcessPool类了,TODO:

  • 对ChannelManager和TaskManager进行封装。
  • 提供给用户插入任务,发布任务等的接口。
  • 开启(Start):创建子进程并使其开始等待任务到达、创建信道并插入ChannelManager。
  • 终止(Stop):销毁信道并回收子进程。
#include "Channel.hpp"
#include "Task.hpp"class ProcessPool
{
public:ProcessPool(int size = 5):_size(size){std::cout << "ProcessPool已创建" << std::endl;}~ProcessPool(){// 假如用户忘记终止并回收进程if(_activate){_CM.CloseAndWait();}}// 子进程转入此函数并循环等待任务到达后执行void Work(int rfd){int code = 0;std::cout << "子进程" << getpid() << "开始工作" << std::endl;while(true){ssize_t n = read(rfd, &code, sizeof(code));if(n == 0) {std::cout << "进程" << getpid() << "退出" << std::endl;break;}else if(n < 0){std::cout << "进程" << getpid() << "获取任务时发生错误" << std::endl;break;}else _TM.GetTask(code)();}}void Start(){for(int i = 0; i < _size; i++){int fds[2] = {0};int n = pipe(fds);if(n == -1){perror("pipe:");}int id = fork();if(id < 0){perror("fork:");exit(1);}else if(id == 0){// 子进程close(fds[1]);Work(fds[0]);close(fds[0]);exit(0);}// 父进程close(fds[0]);_CM.Insert(Channel(fds[1], id));}_activate = true;}// 用户发布任务的接口,交由ChannelManager处理void LaunchTask(int code){assert(code >= 0 && code <= _TM.Size());std::cout << "发布任务: " << code << std::endl;_CM.GiveTask(code);}void Stop(){_CM.CloseAndWait();_activate = false;}// 封装TaskManager的接口,使用户自定义任务void RegisterTask(Task&& task){_TM.RegisterTask(std::forward<Task>(task));}
private:int _size;bool _activate = false;ChannelManager _CM;TaskManager _TM;
};

3.4 Main.cpp

#include "ProcessPool.hpp"
#include <ctime>int main()
{std::cout << "程序启动" << std::endl;srand((unsigned int)time(nullptr));ProcessPool processpool;// 生成n个测试任务int n = 10;for(int i = 0; i < 10; i++){processpool.RegisterTask(([i](){std::cout << "进程" << getpid() << "正在执行任务" << i << std::endl;}));}processpool.Start();// 随机发布10个任务while(n--){int code = rand() % 10;processpool.LaunchTask(code);sleep(2);}processpool.Stop();return 0;
}

 3.5 匿名管道的死锁问题

上面的代码实际上存在一个严重的问题,那就是在10个任务执行结束之后进行信道的销毁时:

在第一个信道提示关闭之后,并没有显式子进程退出的消息,而是直接卡住不动了。查看源代码会发现问题就是出在这一行,说明在信道被关闭之后,子进程并没有退出。

这是由于后创建的子进程继承了父进程对之前创建的子进程的写端口: 

所以,在父进程的视角上关闭信道之后,管道1的写端依然没有完全关闭,子进程就会继续在read处阻塞等待。子进程因等待父进程下达指令或关闭信道而阻塞;父进程因等待子进程退出而阻塞。 此时就形成了死锁,导致程序卡住。

解决方案
  • 方案1:先关闭所有的信道再等待子进程退出。
  • 方案2:逆向关闭信道并退出。
  • 方案3:关闭子进程从父进程那里继承下来的写入端。

4. 最终代码

代码最终采用的是第三种方案,因为该方案的安全性更高,当然前两种方案被部分注释了,读者可以自己尝试修改死锁的解决方案。

4.1 Channel.hpp

#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
#include <iostream>class Channel
{
public:Channel(int wfd, int pid):_wfd(wfd), _process(pid){}~Channel(){}void SubProcessCloseBrother(){close(_wfd);}void Close(){std::cout << "关闭" << _process << "的信道" << std::endl;close(_wfd);std::cout << _process << "的信道关闭成功" << std::endl;}void Wait(){waitpid(_process, nullptr, 0);std::cout << "进程" << _process << "已被成功回收" << std::endl;}// 确保调用该函数的信道为当前最后启动的 或者 事先关闭所有子进程的写入端,否则会造成死锁void CloseAndWait(){close(_wfd);std::cout << _process << "的信道关闭成功" << std::endl;waitpid(_process, nullptr, 0);std::cout << "进程" << _process << "已被成功回收" << std::endl;}void ExecuteTask(int code){std::cout << "将任务" << code << "派遣给" << _process << std::endl;write(_wfd, &code, sizeof(code));}int GetPid(){return _process;}
private:int _wfd;pid_t _process;
};class ChannelManager
{
public:void Insert(Channel&& channel){_Channels.push_back(channel);}int Size(){return _Channels.size();}void GiveTask(int code){int channel = SelectChannel();std::cout << "选择进程: " << _Channels[channel].GetPid() << std::endl;_Channels[channel].ExecuteTask(code);}// 方案1:先关闭后回收void CloseChannels(){for(auto& channel : _Channels){channel.Close();}}void WaitProcesses(){for(auto& channel : _Channels){std::cout << "回收进程" << channel.GetPid() << std::endl;channel.Wait();}}// 方案2:反向关闭回收// void CloseAndWait()// {//     for(int i = _Channels.size() - 1; i >= 0; i--)//     {//         _Channels[i].CloseAndWait();//     }// }// 方案3:关闭所有子进程的写入端,可以任意方式关闭回收void CloseAndWait(){for(auto& channel : _Channels){channel.CloseAndWait();}}void SubProcessCloseBrothers(){for(auto& channel : _Channels){channel.SubProcessCloseBrother();}}private:int SelectChannel(){// 轮询分派任务static int next = 0;assert(_Channels.size());int tmp = next;next = (next + 1) % _Channels.size();return tmp;}std::vector<Channel> _Channels;
};

4.2 Task.hpp

#include <functional>
#include <vector>
#include <iostream>
#include <unistd.h>
#include <cassert>
using Task = std::function<void()>;class TaskManager
{
public:void RegisterTask(Task&& task){_Tasks.push_back(task);}int Size(){return _Tasks.size();}Task& GetTask(int code){assert(code >= 0 && code < _Tasks.size());return _Tasks[code];}
private:std::vector<Task> _Tasks;
};

4.3 ProcessPool.hpp

#include "Channel.hpp"
#include "Task.hpp"class ProcessPool
{
public:ProcessPool(int size = 5):_size(size){std::cout << "ProcessPool已创建" << std::endl;}~ProcessPool(){if(_activate){_CM.CloseChannels();_CM.WaitProcesses();}}void Work(int rfd){int code = 0;std::cout << "子进程" << getpid() << "开始工作" << std::endl;while(true){ssize_t n = read(rfd, &code, sizeof(code));if(n == 0) {std::cout << "进程" << getpid() << "退出" << std::endl;break;}else if(n < 0){std::cout << "进程" << getpid() << "获取任务时发生错误" << std::endl;break;}else _TM.GetTask(code)();}}void Start(){for(int i = 0; i < _size; i++){int fds[2] = {0};int n = pipe(fds);if(n == -1){perror("pipe:");}int id = fork();if(id < 0){perror("fork:");exit(1);}else if(id == 0){// 子进程close(fds[1]);// 将子进程的写入端全部关闭_CM.SubProcessCloseBrothers();Work(fds[0]);close(fds[0]);exit(0);}// 父进程close(fds[0]);_CM.Insert(Channel(fds[1], id));}_activate = true;}void LaunchTask(int code){assert(code >= 0 && code <= _TM.Size());std::cout << "发布任务: " << code << std::endl;_CM.GiveTask(code);}void Stop(){// _CM.CloseChannels();// _CM.WaitProcesses();_CM.CloseAndWait();_activate = false;}void RegisterTask(Task&& task){_TM.RegisterTask(std::forward<Task>(task));}
private:int _size;bool _activate = false;ChannelManager _CM;TaskManager _TM;
};

4.4 Makefile

ProcessPool:Main.cppg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm ProcessPool

相关文章:

Linux笔记---进程间通信:匿名管道

1. 管道通信 1.1 管道的概念与分类 管道&#xff08;Pipe&#xff09; 是进程间通信&#xff08;IPC&#xff09;的一种基础机制&#xff0c;主要用于在具有亲缘关系的进程&#xff08;如父子进程、兄弟进程&#xff09;之间传递数据&#xff0c;其核心特性是通过内核缓冲区实…...

JAVA设计模式——(三)桥接模式

JAVA设计模式——&#xff08;三&#xff09;桥接模式&#xff08;Bridge Pattern&#xff09; 介绍理解实现武器抽象类武器实现类涂装颜色的行为接口具体颜色的行为实现让行为影响武器修改武器抽象类修改实现类 测试 适用性 介绍 将抽象和实现解耦&#xff0c;使两者可以独立…...

设计模式--工厂模式详解

工厂模式 作用&#xff1a; 实现了创建者与调用者的分离 详细分类 简单工厂模式 工厂方法模式 抽象工厂模式 OOP七大原则&#xff1a; 开闭原则&#xff1a;一个软件的实体应该对拓展开发&#xff0c;对修改关闭 依赖反转原则&#xff1a;要针对接口编程&#xff0c;不…...

每天五分钟深度学习PyTorch:图像的处理的上采样和下采样

本文重点 在pytorch中封装了上采样和下采样的方法,我们可以使用封装好的方法可以很方便的完成采样任务,采样分为上采样和下采样。 上采样和下采样 下采样(缩小图像)的主要目的有两个:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。 下采样( 放大图像)的…...

前端面试场景题

目录 1.项目第一次加载太慢优化 / vue 首屏加载过慢如何优化 2.说说了解的es6-es10的东西有哪些 ES6&#xff08;ES2015&#xff09;之后&#xff0c;JavaScript 新增了许多实用的数组和对象方法&#xff0c;下面为你详细介绍&#xff1a; 3.常见前端安全性问题 XSS&#…...

国际化不生效

经过我的重重检查 最终发现是 版本问题。 原本下载默认next版本cnpm install vue-i18nnext 下载 国际化插件 cnpm install vue-i18n^9.14.3 删除掉node_models&#xff0c;再重新加载包&#xff1a;cnpm install 这时候就可以正常显示了 国际化操作&#xff1a; en.js zh…...

新一代人工智能驱动医疗数智化:范式变革、实践方向及路径选择

人工智能(AI)正以前所未有的速度重构医疗健康行业的底层逻辑,从数据获取、知识建模到临床决策支持,AI不仅是“辅助工具”,更日益成为医疗生产力体系的核心引擎。随着大模型、计算平台和数智基础设施的迅猛发展,医疗数智化正进入从“点状创新”走向“系统重构”的深水区。…...

OpenCV 图形API(55)颜色空间转换-----将图像从 RGB 色彩空间转换为 I420 格式函数RGB2I420()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从 RGB 色彩空间转换为 I420 色彩空间。 该函数将输入图像从 RGB 色彩空间转换为 I420。R、G 和 B 通道值的常规范围是 0 到 255。 输出图…...

大模型安全吗?数据泄露与AI伦理的黑暗面!

大模型安全吗&#xff1f;数据泄露与AI伦理的黑暗面&#xff01; 随着人工智能技术的飞速发展&#xff0c;尤其是大型语言模型&#xff08;如GPT-3、BERT等&#xff09;的出现&#xff0c;AI的应用场景越来越广泛&#xff0c;从智能客服到内容生成&#xff0c;从医疗诊断到金融…...

穿越链路的旅程:深入理解计算机网络中的数据链路层

一、引言 在计算机网络的七层模型中&#xff0c;数据链路层&#xff08;Data Link Layer&#xff09; 是连接物理世界与逻辑网络世界的关键一环。它位于物理层之上&#xff0c;网络层之下&#xff0c;负责将物理层的“比特流”转换成具有结构的数据帧&#xff0c;并确保数据在…...

《AI大模型应知应会100篇》第35篇:Prompt链式调用:解决复杂问题的策略

第35篇&#xff1a;Prompt链式调用&#xff1a;解决复杂问题的策略 摘要 在大模型应用中&#xff0c;单次提示的能力往往受限于上下文长度和任务复杂度。为了解决这些问题&#xff0c;Prompt链式调用应运而生。本文将深入探讨如何通过分解任务、设计逻辑链路、传递中间结果&am…...

管理100个小程序-很难吗

20公里的徒步-真难 群里的伙伴发起了一场天目山20公里徒步的活动&#xff0c;想着14公里都轻松拿捏了&#xff0c;思考了30秒后&#xff0c;就借着春风带着老婆孩子就出发了。一开始溪流清澈见底&#xff0c;小桥流水没有人家&#xff1b;青山郁郁葱葱&#xff0c;枯藤老树没有…...

算法恢复训练-Part01-数组

注&#xff1a;参考的某算法训练营的计划 核心注意点 在 Golang&#xff08;和大多数主流语言&#xff0c;如 C/C&#xff09;中&#xff0c;二维数组按行访问的效率更高。因为它符合 Go 的内存连续存储结构&#xff0c;能提高 CPU Cache 命中率&#xff0c;减少内存跳跃带来…...

软件黑盒与白盒测试详解

黑盒测试与白盒测试的核心对比 一、定义与核心目标 黑盒测试 定义&#xff1a;将程序视为“黑盒”&#xff0c;仅通过输入和输出验证功能是否符合需求规格&#xff0c;不关注内部代码逻辑。目标&#xff1a;确保功能完整性、输入输出正确性及用户体验&#xff0c;例如验证购物车…...

本文通俗简介-优雅草星云物联网AI智控系统软件介绍-星云智控是做什么用途的??-优雅草卓伊凡

本文通俗简介-优雅草星云物联网AI智控系统软件介绍-星云智控是做什么用途的&#xff1f;&#xff1f;-优雅草卓伊凡 星云智控&#xff1a;物联网设备实时监控的革新力量 一、引言 在科技飞速发展的当下&#xff0c;物联网技术的广泛应用使得各类设备的实时监控与管理变得愈发…...

达梦统计信息收集情况检查

查询达梦某个对象上是否有统计信息 select id,T_TOTAL,N_SMAPLE,N_DISTINCT,N_NULL,BLEVEL,N_LEAF_PAGES,N_LEAF_USED_PAGES,LAST_GATHERED from sysstats where id IN (select id from sysobjects where upper(name)upper(&objname));可能有系统对象&#xff0c;可以增加…...

【MQ篇】RabbitMQ之发布订阅模式!

目录 引言一、 回顾&#xff1a;简单模式与工作队列模式的局限 &#x1f614;二、 发布/订阅模式详解&#xff1a;消息的“广播站” &#x1f4fb;三、 RabbitMQ 中的交换机类型&#xff1a;不同的“广播方式” &#x1f4fb;四、 Java (Spring Boot) 代码实战Fanout 模式的完整…...

如何批量为多张图片(JPG、PNG、BMP、WEBP 等格式)添加自定义水印保护

「鹰迅批量处理工具箱」提供了强大的批量水印添加功能&#xff0c;支持常见的图片格式&#xff0c;如 JPG、JPEG、PNG、BMP、GIF、WEBP 等。用户不仅可以选择添加文字水印或图片水印&#xff0c;还能自定义设置水印的样式、位置和透明度等参数&#xff0c;操作简单而高效&#…...

LeetCode每日一题4.23

题目 问题分析 计算每个数字的数位和&#xff1a;对于从 1 到 n 的每个整数&#xff0c;计算其十进制表示下的数位和。 分组&#xff1a;将数位和相等的数字放到同一个组中。 统计每个组的数字数目&#xff1a;统计每个组中有多少个数字。 找到并列最多的组&#xff1a;返回数…...

Kafka简介

简介 基本概念 Kafka是分布式发布 - 订阅消息系统&#xff0c;最初由LinkedIn开发&#xff0c;后成为Apache项目一部分&#xff0c;可类比为放鸡蛋的篮子&#xff0c;生产者产蛋放入&#xff0c;消费者从中取蛋 。 消息系统 优势&#xff1a;分布式系统&#xff0c;易扩展&am…...

大数据利器:Kafka与Spark的深度探索

在大数据领域&#xff0c;Kafka和Spark都是极为重要的工具。今天就来和大家分享一下我在学习和使用它们过程中的心得。 Kafka作为分布式消息系统&#xff0c;优势显著。它吞吐量高、延迟低&#xff0c;能每秒处理几十万条消息&#xff0c;延迟最低仅几毫秒&#xff1b;可扩展性…...

使用logrotate实现日志轮转

logrotate 是一个强大的 Linux 工具&#xff0c;用于自动化管理日志文件的轮转、压缩、删除和归档。它能有效防止日志文件无限增长&#xff0c;节省磁盘空间&#xff0c;同时保持日志的可追溯性。以下是详细讲解 logrotate 的用法&#xff0c;涵盖安装、配置、测试、自动化、常…...

第52讲:农业AI + 区块链——迈向可信、智能、透明的未来农业

目录 一、为什么农业需要“AI+区块链”? 二、核心应用场景解读 1. 农产品溯源系统 2. 农业信贷与保险精准评估 3. 农业碳足迹追踪与碳汇交易 三、案例实战分享:智能溯源 + 区块链合约 四、面临挑战与展望 五、总结 在数字农业时代,“AI” 和 “区块链” 是两股不容忽…...

视频智能分析平台EasyCVR无线监控:全流程安装指南与功能应用解析

在当今数字化安防时代&#xff0c;无线监控系统的安装与调试对于保障各类场所的安全至关重要。本文将结合EasyCVR视频监控的强大功能&#xff0c;为您详细阐述监控系统安装过程中的关键步骤和注意事项&#xff0c;帮助您打造一个高效、可靠的监控解决方案。 一、调试物资准备与…...

Spring Cloud Eureka 与 Nacos 深度解析:从架构到对比

一、Eureka&#xff1a;经典微服务注册中心 &#xff08;一&#xff09;核心定位与特性 Spring Cloud Eureka 是 Netflix 开源的服务注册与发现组件&#xff0c;在微服务架构中扮演 "大脑" 角色&#xff0c;负责服务的注册、发现与状态管理。其核心优势在于通过心跳…...

深入详解Java中的@PostConstruct注解:实现简洁而高效初始化操作

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…...

【Unity笔记】Unity 编辑器扩展:一键查找场景中组件引用关系(含完整源码)(组件引用查找工具实现笔记)

摘要&#xff1a; 本文介绍了如何在 Unity 编辑器中开发一款实用的编辑器扩展工具 —— ComponentReferenceFinder&#xff0c;用于查找场景中对某个自定义组件的引用关系。该工具特别适用于大型项目、多人协作或引入外部插件后&#xff0c;快速定位组件间的耦合关系。 本文从需…...

实体店的小程序转型之路:拥抱新零售的密码-中小企实战运营和营销工作室博客

实体店的小程序转型之路&#xff1a;拥抱新零售的密码-中小企实战运营和营销工作室博客 在当今数字化浪潮的冲击下&#xff0c;实体店面临着前所未有的挑战&#xff0c;但小程序的出现为实体店转型新零售带来了新的曙光。先来看一组惊人的数据&#xff0c;据相关统计&#xff…...

Mysql安装与备份配置分析

若之前存有msqyl的数据缓存&#xff0c;建议用以下命令将数据文件删除干净 mysql-server&#xff1a;主程序 mysql&#xff1a;客户端工具 mysql-devel&#xff1a;开发库 mysql-libs&#xff1a;共享库文件 /var/lib/mysql&#xff1a;数据目录 /etc/my.cnf : 主配置文件 …...

Android APP 爬虫操作

工具 夜神模拟器、charles、mitm 等 mitm的使用参考:Mitmproxy对Android进行抓包&#xff08;真机&#xff09;_mitmproxy 安卓-CSDN博客 charles的使用参考&#xff1a;【全网最详细】手把手教学Charles抓包工具详细自学教程&#xff0c;完整版安装教程&#xff0c;详细介绍…...

与Ubuntu相关命令

windows将文件传输到Ubuntu 传输文件夹或文件 scp -r 本地文件夹或文件 ubuntu用户名IP地址:要传输到的文件夹路径 例如&#xff1a; scp -r .\04.py gao192.168.248.129:/home/gao 如果传输文件也可以去掉-r 安装软件 sudo apt-get update 更新软件包列表 sudo apt insta…...

Unity常用内置变换矩阵

Unity引擎提供了一系列内置的变换矩阵&#xff0c;这些矩阵在着色器中用于处理物体、摄像机和光照的坐标变换&#xff0c;是游戏开发中不可或缺的工具。它们帮助开发者在顶点着色器和片段着色器中实现坐标转换、光照计算等功能。 主要变换矩阵类型 模型矩阵 (Model Matrix) /…...

算法题-图论

图的表示 207.课程表 127.单词接龙 图的遍历 DFS 递归。。。 200.岛屿数量 239.矩阵中的最长递增路径 BFS 102.二叉树的层序遍历 看到最短&#xff0c;首先想到的是BFS 542.01矩阵 207.课程表 127.单词接龙 拓扑排序 对于一个有向无环图G进行拓扑排序&#xff0c;是将G中…...

Java 8(Ubuntu 18.04.6 LTS)安装笔记

一、前言 本文与【MySQL 8&#xff08;Ubuntu 18.04.6 LTS&#xff09;安装笔记】同批次&#xff1a;先搭建数据库&#xff0c;再安装JVM&#xff0c;后面肯定就是部署Web应用了——典型的单机部署&#xff0c;真可谓“麻雀虽小五脏俱全”。 二、准备 &#xff08;1&#xff…...

unity编辑器的json验证及格式化

UNITY编辑器的json格式化和验证工具资源-CSDN文库https://download.csdn.net/download/qq_38655924/90676188?spm1001.2014.3001.5501 反复去别的网站验证json太麻烦了 用这个工具能方便点 # Unity JSON工具 这是一个Unity编辑器扩展&#xff0c;用于验证、格式化和压缩JSO…...

C语言中小写字母转大写字母

一、题目引入 这一题运行结果是什么? 二、代码分析 在这个代码中 首先 -> 定义了一个字符数组空间内存是80 里面存储的是字符串123abcdEFG*& 接着 -> 定义了一个整型变量j 后面的循环会用到 然后 -> 使用了<stdio.h>中的库函数puts(ch)原样打印…...

深度学习--卷积神经网络调整学习率

文章目录 前言一、学习率1、什么学习率2、什么是调整学习率3、目的 二、调整方法1、有序调整1&#xff09;有序调整StepLR(等间隔调整学习率)2&#xff09;有序调整MultiStepLR(多间隔调整学习率)3&#xff09;有序调整ExponentialLR (指数衰减调整学习率)4&#xff09;有序调整…...

桥接模式:分离抽象与实现的独立进化

桥接模式&#xff1a;分离抽象与实现的独立进化 一、模式核心&#xff1a;解耦抽象与实现的多层变化 在软件设计中&#xff0c;当抽象&#xff08;如 “手机品牌”&#xff09;和实现&#xff08;如 “操作系统”&#xff09;都可能独立变化时&#xff0c;使用多层继承会导致…...

配置kafka与spark连接

一、配置kafka 首先进到software目录当中&#xff0c;如下图所示&#xff1a; 安装包上传/解压/重命名/解压过后的目录如下图所示&#xff1a; 修改配置&#xff1a; cd config vi server.properties 全部修改语句如下所示&#xff08;以node01为样例&#xff09;&#xff1…...

WebXR教学 05 项目3 太空飞船小游戏

准备工作 自动创建 package.json 文件 npm init -y 安装Three.js 3D 图形库&#xff0c;安装现代前端构建工具Vite&#xff08;用于开发/打包&#xff09; npm install three vite 启动 Vite 开发服务器&#xff08;推荐&#xff09;&#xff08;正式项目开发&#xff09; …...

【leetcode】3524 求出数组的X值1

题目链接 题目描述 给你一个正整数数组 nums 和一个正整数 k。 你可以对数组执行一次操作&#xff1a;移除不重叠的前缀和后缀&#xff08;可以为空&#xff09;&#xff0c;留下一个连续非空子数组。 对于每一种留下的子数组&#xff0c;计算&#xff1a; (该子数组的乘积…...

配电室安全用电漏电保护装置的安全用电措施

配电室作为电力分配与控制的关键场所&#xff0c;其安全用电装置的重要性不言而喻。 防触电、防漏电、防火灾、安全防护、保障生命财产&#xff1b; 当用电设备发生短路、过载或漏电等异常时能迅速切断电源&#xff0c;精准定位问题。及时报警&#xff0c;防止触电 在配电室中…...

数据库-基本概述 和 SQL 语言

标题目录 基本概述DB和DBMS关系数据库库与表的概念表库 数据库在项目中的角色如何操作数据库 SQL 语言SQL 分类DDL 语言数据库操作创建数据库查看数据库删除数据库切换数据库 表的操作创建表查看表修改表名删除表修改表结构 DML 语言插入数据修改数据删除数据 基本概述 DB和DB…...

C语言中的递归1.0

一、递归函数概念引入 简单来说就是自己调用自己 递归函数满足的两个条件: 1.每次调用函数本身,必须一次又一次接近最终结果 2.必须有停止条件 二、代码展示 用递归求1到4的和 代码如下 三、代码分析 首先进入main主函数入口 int sum getsum(4); 就这个具体题目来看 我…...

解锁webpack:对html、css、js及图片资源的抽离打包处理

面试被问到webpack&#xff0c;可别只知道说 HtmlWebpackPlugin 了哇。 前期准备 安装依赖 npm init -y npm install webpack webpack-cli --save-dev配置打包命令 // package.json {"scripts": {// ... 其他配置信息"build": "webpack --mode pr…...

[特殊字符] 大模型对话风格微调项目实战——模型篇 [特殊字符]✨

&#x1f4dc; 目录 &#x1f3af; 背景介绍 &#x1f50d; 这篇文章的任务 &#x1f916; 模型选型 &#x1f4ca; 模型评测 ⚙️ 模型训练 &#x1f504; 模型转换 &#x1f9ea; 模型训练效果评估 &#x1f389; 总结 &#x1f3af; 背景介绍 本文是《大模型对话风…...

lerobot[act解析]

ACT是具身智能模仿学习中重要的一个算法&#xff0c;本文会先从这个算法是是什么&#xff0c;这个算法如何工作的&#xff0c;到这个算法为什么有效&#xff0c;也就是what->how->why的这么一个顺序来进行解析 ACT 是什么&#xff1f;&#xff08;What&#xff09; 核心…...

使用Python创建带边框样式的Word表格

引言 在生成Word文档时&#xff0c;表格的边框样式是提升专业度的重要细节。本文将通过一个实例&#xff0c;展示如何使用python-docx库为表格添加上下边框加粗和内部边框隐藏的复杂样式。代码将实现以下效果&#xff1a; 表格位于页面底部表格首行和末行的上下边框加粗隐藏内…...

GPLT-2025年第十届团体程序设计天梯赛总决赛题解(共计266分)

今天偶然发现天梯赛的代码还保存着&#xff0c;于是决定写下这篇题解&#xff0c;也算是复盘一下了 L1本来是打算写的稳妥点&#xff0c;最后在L1-6又想省时间&#xff0c;又忘记了insert&#xff0c;replace这些方法怎么用&#xff0c;也不想花时间写一个文件测试&#xff0c…...

基于SpringBoot的课程管理系统

前言 今天给大家分享一个基于SpringBoot的课程管理系统。 1 系统介绍 课程管理系统是一种专门为学校设计的软件系统&#xff0c;旨在帮助学校高效地管理和组织各类课程信息。 该系统通常包括学生、教师和管理员三大角色。 他们可以通过系统进行选课、查看课程表、考试、进…...