【Linux--进程间通信】
进程间通信介绍
进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
 - 资源共享:多个进程之间共享同样的资源
 - 通知事件:一个进程需要向另一个或一组进程发送消息。通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
 - 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够即是知道它的状态改变。
 
进程间通信发展
管道
- 匿名管道pipe
 - 命名管道
 
system V IPC
- System V 消息队列
 - System V 共享内存
 - System V 信号量
 
POSIX IPC
- 消息队列
 - 共享内存
 - 信号量
 - 互斥量
 - 条件变量
 - 读写锁
 
管道
什么是管道
管道是Unix中最古老的进程间通信
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
匿名管道
mypipe.cc
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种进程间通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景const std::string namestr="hello 我是子进程";int cnt = 1;char buffer[1024];while(true){snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());write(pipefd[1],buffer,strlen(buffer));sleep(1);}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;while(true){sleep(1);int n = read(pipefd[0],buffer,sizeof(buffer));if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}}return 0;
}
 
Makefile
mypipe:mypipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf mypipe
 
运行结果
 
 可以看到父进程读到了子进程中的内容,同时父进程读取的速度和写入的速度是相同的。
管道的特点
- 单向通信
 - 管道的本质是文件,因为fd的生命周期随进程,管道的生命周期是随进程的。
 - 管道通信,通常用来进行具有“血缘”关系的进程,进行程序间通信,常用于父子通信–pipe打开管道,并不清楚管道的名字,叫做匿名管道。
 - 在管道通信中,写入的次数和读取的次数,不是严格匹配的,读写次数的多少没有强相关–表现–字节流
 
4种场景
- 如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待。
 - 如果我们write端将管写满了,我们就不能继续写了。
 
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;while(true){sleep(1);int n = read(pipefd[0],buffer,sizeof(buffer));if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}}return 0;
}
 
运行结果:
 
 3. 如果我们关闭了写端,读取完毕管道数据,再读就会read返回0,表明读到了文件结尾。
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/wait.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;sleep(1);break;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;int cnt = 0;while(true){int n = read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读异常了"<<std::endl;break;}sleep(1);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id,&status,0);std::cout<<"sig:"<<(status & 0x7F)<<std::endl;//sleep(100);return 0;
}
 
运行结果:
 
- 如果写端一直写,读端关闭会发生什么呢?没有意义,操作系统不会维护无意义、低效率或者浪费资源的事情。OS会杀死一直在写入的进程!OS会通过信号来终止进程。13)SIGPIPE
 
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/wait.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;sleep(1);//break;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;int cnt = 0;while(true){int n = read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读异常了"<<std::endl;break;}sleep(1);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id,&status,0);std::cout<<"sig:"<<(status & 0x7F)<<std::endl;sleep(100);return 0;
}
 
运行结果:

 可以看到子进程退出后,由父进程通过waitpid读到子进程退出码。
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
 - 管道提供流式服务。
 - 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
 - 一般而言,内核会对管道操作进行同步与互斥。
 - 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
 
命名管道
- 管道应用的一个限制就是只能再基友共同祖先(具有亲缘关系)的进程间通信。
 - 如果我们想不在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它被称为命名管道。
 - 命名管道是一种特殊类型的文件。
 
创建一个命名管道
命名管道可以从命令行上创建,命令行方式是使用下面这个方式。
mkfifo filename
 
命名管道也可以从程序里面创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
 
创建命名管道:
int main(int argc,char *argv[])
{mkfifo("p2",0644);return 0;
} 
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开
 - 命名管道由mkfifo函数创建,打开用open
 - FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于他们创建与打开的方式不同,一旦这些工作完成之后,他们具有相同的语义。
 
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
 - O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时 - O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
 - O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
 
system V共享内存
共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到 内核,换句话说是进程不在通过执行进入内核的系统调用来传递彼此的数据。
 共享内存示意图
 
共享内存数据结构
共享内存函数
 shmget函数
功能:用来创建共享内存
 原型:int shmget(key_t key,size_t size,int shmflg);
 参数
- key(共享内存段名字)
 - size(共享内存大小)
 - shmflg(由九个权限标志构成,他们的用法和创建文件时使用的mode模式标志是一样的
 
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到地址空间
 原型:void *shmat(int shmid,const void *shmaddr,int shmflg);
 参数
- shmid:共享内存标识
 - shmaddr:指定连接的地址
 - shmflg:它的两个可能取值是SHM_RND 和SHM_RDONLY
 
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
 说明
- shmaddr为NULL,核心自动选择一个地址
 - shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
 - shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr(shmaddr % SHMLBA)
 - shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
 
进程间通信的前提是让不同的进程看到同一份资源
 ipcs函数
 
下面来看一段代码
 comm.hpp
#ifndef __COM_HPP__#define __COM_HPP__#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<cerrno>
#include<cstring>
#include<cstdio>
#include<string>using namespace std;//单独使用IPC_CREAT:创建一个共享内存,如果共享内存不存在,就创建;如果已经存在获取已经存在的共享内存并返回
//IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
//IPC_CREAT|IPC_EXCL:创建一个共享内存,如果共享内存不存在,就创建;如果已经存在,就立马出错返回//只要没出错就是成功创建了新的共享内存#define PATHNAME "."
#define PROID 0x6666const int gsize=4096;//字节为单位
key_t getkey()
{key_t k = ftok(PATHNAME,PROID);if(k==-1){cerr<<"error:"<<errno<<":"<<strerror(errno)<<endl;exit(1);}return k;
}
string toHex(int x)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%x",x);return buffer;
}
static int createShmHelper(key_t k,int size,int flag)
{int shmid = shmget(k,gsize,flag);if(shmid==-1){cerr<<"error:"<<errno<<":"<<strerror(errno)<<endl;exit(2);}return shmid;
}
int createShm(key_t k,int size)
{return createShmHelper(k,size,IPC_CREAT|IPC_EXCL);
}
int getShm(key_t k,int size)
{dreturn createShmHelper(k,size,IPC_CREAT);
}#endif 
 
server.cc
#include"comm.hpp"int main()
{//创建keykey_t k = getkey();cout<<"server key:"<<toHex(k)<<endl;//2、创建共享内存int shmid = createShm(k,gsize);cout<<"server shmid:"<<toHex(shmid)<<endl;return 0;
}
 
client.cc
#include"comm.hpp"int main()
{key_t k = getkey();cout<<"client key:"<<toHex(k)<<endl;int shmid = getShm(k,gsize);cout<<"client shmid:"<<toHex(shmid)<<endl;return 0;
}
 
Makefile
.PHONY:all
all:client serverclient:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server 
编译运行
 
通过ipcs查看信息
 
 当我们重新编译运行程序时,我们看到打印了错误信息,并且返回值为2

 也就是说共享内存创建失败
 
 进一步分析我们不难得知是因为共享内存在进程关闭之后还存在,所以无法创建新的。
 即是共享内存生命周期不随进程、随OS
 
那么我们应该怎么删除共享内存呢?
 ipcrm
 
 我们可以用shmid删除共享内存
 
 我们也可以使用key来删除共享内存

key VS shmid
| key | shmid | 
|---|---|
| 类比:文件系统里的inode | 类比:文件系统里的fd | 
| 系统层面 | 用户层面 | 
shmctl函数
功能:用于控制共享内存
 原型:int shmctl(int shm,int cmd,struct shmid_ds *buf);
 参数
- shmid:由shmget返回的共享内存标识码
 - cmd:将要采取的动作(有三个可取值)
 - buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
 
返回值:成功返回0,失败返回-1
| 命令 | 说明 | 
|---|---|
| IPC_STAT | 把shmid_ds结构中的数据设置为共享内存的当前关联值 | 
| IPC_SET | 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 | 
| IPC_RMID | 删除共享内存段 | 
注意
- 由于共享内存大小是向上对齐PAGE(4KB),使用者申请多少就给多少。
 - 我们在通信的时候,没有使用任何接口。一旦共享内存映射到进程的地址空间,该共享内存就直接被所有的进程直接看到了!由于这种特性,可以让进程通信的时候,减少拷贝次数,所以共享内存是所有进程间通信最快的一种。
 - 共享进程没有任何的保护机制(同步互斥),虽然速度快但是准确性会受到影响。

 
相关文章:
【Linux--进程间通信】
进程间通信介绍 进程间通信目的 数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源通知事件:一个进程需要向另一个或一组进程发送消息。通知它(它们)发生了某种事件(如…...
Linux C文件操作
文章目录 文件操作函数文件系统调用系统调用与标准函数c的调用的区别文件的读取位置标准c函数系统调用空洞文件 文件的内存映射操作文件目录 linux下的文件操作包括两种,一种是使用C函数,一种是使用系统调用。 gcc 常用来实现c程序的编译gcc filename.c …...
基于Cucumber的行为驱动开发(BDD)实例
本篇介绍 Cucumber 的基本使用, 因为Cucumber是BDD的工具, 所以首先需要弄清楚什么是BDD,而在介绍BDD之前,先看看常见的软件开发方法。 常见的软件开发方法 面向过程开发(Procedural Development)&#x…...
十六、代码校验(2)
本章概要 前置条件 断言(Assertions)Java 断言语法Guava 断言使用断言进行契约式设计检查指令前置条件后置条件不变性放松 DbC 检查或非常严格的 DbCDbC 单元测试 前置条件 前置条件的概念来自于契约式设计(Design By Contract, DbC), 利用断言机制…...
安卓 kotlin-supportFragmentManager报红
如果你继承baseActivity 请查看 是不是继承 AppCompatActivity...
linux中安装RocketMQ以及dashboard
前提: 需要安装jdk8 上传下面的文件到服务器中 新建目录 mkdir rocketmq 将下载后的压缩包上传到阿里云服务器或者虚拟机中去,并解压 unzip rocketmq-all-4.9.2-bin-release.zip 配置环境变量 vim /etc/profile 配置内容: export NAM…...
Android kotlin内联函数(inline)的详解与原理
一、介绍 在kotlin中,有一种函数叫内联函数,这种函数标识符是inline,但是好多人对这个函数的理解只停留在八股文中,内容函数的用法和普通函数没有区别,但是在编译原理上是有,对程序的性能有一定的影响。 二…...
林沛满---一个面试建议
在应聘一个技术职位之前,做好充分的准备无疑能大大提高成功率。这里所说的准备并不是指押题,因为有经验的面试官往往准备了海量的题库,押中的概率太低。比如我有位同事的题库里有上百道题,内容涵盖了编程、操作系统、网络、存储……...
CMake教程-第 5 步:安装和测试
CMake教程-第 5 步:安装和测试 1 CMake教程介绍2 学习步骤Step 1: A Basic Starting PointStep 2: Adding a LibraryStep 3: Adding Usage Requirements for a LibraryStep 4: Adding Generator ExpressionsStep 5: Installing and TestingStep 6: Adding Support f…...
移动应用-Android开发基础\核心知识点
Android开发基础 知识点 1 介绍了解2 系统体系架构3 四大应用组件4 移动操作系统优缺点5 开发工具6 配置工具7 下载相关资源8JDK下载安装流程9配置好SDK和JDK环境10 第一个Hello word11 AS开发前常用设置12模拟器使用运行13 真机调试14 AndroidUI基础布局15 加载展示XML布局16…...
Java读取并转换字符串中的浮点数
在写Android接收蓝牙数据的时候,由于传过来的蓝牙数据转换后都为字符串格式,但是需要从其中提取出来浮点数,所以通过查阅资料写出了从字符串中提取并转换为浮点数的方法,特记录下来以供参考。 目录 原始数据内容 提取字符串中的…...
SQL: 索引原理与创建索引的规范
SQL 索引是一种数据结构,用于加速数据库查询操作。它通过在表的列上创建索引,提供了一种快速查找数据的方法,减少了数据库的扫描和比较操作,从而提高了查询性能。索引根据其实现方式可以分为多种类型,如 B-树索引、哈希…...
基于STM32_DS18B20单总线传感器驱动
基于STM32_DS18B20单总线传感器驱动 文章目录 基于STM32_DS18B20单总线传感器驱动前言一、BS18B20?二、原理1.复位与检验2.基本命令3.唯一ROM识别码4.温度转换 三、驱动代码四、注意事项 前言 本文以一款典型的单总线传感器及其驱动——DS18B20为例,简单…...
目标识别项目实战:基于Yolov7-LPRNet的动态车牌目标识别算法模型(三)
前言 目标识别如今以及迭代了这么多年,普遍受大家认可和欢迎的目标识别框架就是YOLO了。按照官方描述,YOLOv8 是一个 SOTA 模型,它建立在以前 YOLO 版本的成功基础上,并引入了新的功能和改进,以进一步提升性能和灵活性…...
springboot线程池创建与使用
/*** author: zcs* Title: TaskPoolConfig* Description: 线程池配置* date: 2023/10/11 17:52*/ Component public class TaskPoolConfig {Bean(name "threadPoolTaskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor taskExecutor new ThreadP…...
UDP和TCP特点(部分)对比:
传输层的两个主要协议:TCP 和 UDP UDP和TCP特点(部分)对比: UDP:无连接, 不可靠传输, 面向数据报, 全双工。 TCP:有连接, 可靠传输, 面向字节流…...
PostMan环境变量、全局变量、动态参数使用
一、环境准备 postmanmoco [{"description": "登录认证","request": {"uri": "/login","method": "post","forms": {"user": "admin","password": "a123…...
服务器数据恢复-服务器硬盘指示灯黄灯闪烁的数据恢复案例
服务器数据恢复环境: 服务器面板上的硬盘指示灯显示黄色是一种警告,提示指示灯对应的服务器硬盘已经被服务器识别出存在故障,硬盘即将下线。如果出现这种情况,建议服务器管理员/运维人员及时用完好的硬盘替换显示黄色指示灯对应的…...
ts 分发
在 TypeScript 中,"分发"(distributive)是指在条件类型中的联合类型上自动进行类型推断的机制。当使用条件类型操作联合类型时,TypeScript 会自动将联合类型中的每个成员都应用该条件类型。 下面是一个示例:…...
SQL中的group by使用注意事项
在 SQL 中,GROUP BY 语句用于将查询结果按照指定的列进行分组,并对每个分组计算聚合函数(如 SUM、AVG、COUNT 等)的值。一般情况下,如果查询中包含聚合函数,那么就需要使用 GROUP BY 语句将查询结果按照指定…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

