【Linux】进程间通信(无名/有名管道及System V共享内存)

news/2024/4/26 1:05:25/文章来源:https://blog.csdn.net/gfdxx/article/details/129210780


目录

一、通信的相关概念

二、管道(半双工)

1、管道的概念

三、匿名管道(fork实现,用于父子及血缘关系进程间通信)

1、匿名管道的使用

2、匿名管道的读写情况

3、管道的特征

4、基于匿名管道的进程池

四、命名管道(open打开相同文件实现,可用于非血缘关系的进程间通信)

1、命名管道的创建及删除

1.1命令行创建命名管道

1.2程序内创建及删除命名管道

1.3基于命名管道的用户端发送,服务端接收

五、System V共享内存

1、共享内存(物理内存块+相关属性)的原理

2、共享内存相关命令

2.1查看共享内存(ipcs -m/-q/-s)

2.2删除共享内存(ipcrm -m shmnid)

3、创建/查看/删除/控制(删除)/关联共享内存

3.1形成key(ftok)

3.2创建共享内存(shmget)

3.3关联/卸载共享内存(shmat/shmdt)(关联类似malloc)

3.4控制(主要用移除)共享内存(shmctl)

4、利用共享内存进行进程间通信

5、共享内存的优缺点

5.1共享内存的优点

5.2共享内存的缺点

5.3共享内存的特点

5.4共享内存大小的建议


一、通信的相关概念

进程之间具有独立性,进程间如果要发生通信,就需要打破这种独立性。进程间通信必定需要一块公共的区域用来作为信息的存放点,操作系统需要直接的或间接给通信进程双方提供内存空间,例如这块内存空间是文件系统提供的,那么就是管道通信,通信的本质就是让不同的进程看到同一份内存空间。

进程间通信是为了完成:

1、数据传输:一个进程需要将它的数据发送给另一个进程;

2、资源共享:多个进程之间共享相同的资源;

3、事件通知:一个进程需要向另一个或另一组进程发送消息,通知他们发送了某种事件(例如子进程终止时要通知父进程)

4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

二、管道(半双工)

1、管道的概念

管道是基于文件系统的通信方式,那么就从底层的角度看一下管道通信的原理:

管道文件是内存级文件,不用访问磁盘进行文件加载,操作系统直接创建结构体对象及内核缓冲区。如上图例子,管道文件不必使用open进行打开,操作系统会创建文件结构体对象及其内核缓冲区,并将其放入父进程的文件描述符表中,父进程创建子进程后,父子进程便能基于管道这个内存级文件进行通信。

管道只能单向通信

三、匿名管道(fork实现,用于父子及血缘关系进程间通信)

通过上方管道的概念可知,通过父进程fork创建子进程,让子进程拷贝父进程中管道文件的地址,两个进程便能看到同一个管道文件,这个管道文件是一个内存级文件,并没有名字,所以被称为匿名管道。

所以对待管道和对待文件一样,体现Linux一切皆文件的思想。

1、匿名管道的使用

#include <unistd.h>
int pipe(int pipefd[2]);//pipefd[2]是输出型参数
On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
成功时返回零,错误时返回 -1,并适当地设置 errno。

pipefd[2]是输出型参数,外边搞个pipefd[2]数组传进去,系统调用pipe结束后这个数组中存放的就是读/写的fd。

子进程写入数据到管道,父进程读取管道数据代码。

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
//父进程读取,子进程写入
int main()
{//第一步父进程创建管道int fds[2];int n=pipe(fds);assert(n==0);//第二步父进程fork()创建子进程pid_t id =fork();//fork之后,父进程返回值>0,子进程返回值==0assert(id>=0);const char* s="我是子进程,我的pid是:";int cnt=0;if(id==0){close(fds[0]);//子进程关闭读取的fd//子进程的通信代码while(true){char buffer[1000];//这个缓冲区只有子进程能看到snprintf(buffer,sizeof(buffer),"子进程第%d次向父进程发送:%s%d",++cnt,s,getpid());//向缓冲区buffer中打印write(fds[1],buffer,strlen(buffer));//子进程将缓冲区数据写入管道sleep(1);//每隔1秒写一次//break;}close(fds[1]);//如果break跳出循环,子进程将关闭写端exit(0);}close(fds[1]);//父进程关闭写入 //父进程的通信代码while(true){char buffer[1000];//这个缓冲区只有父进程能看到//如果管道中没有数据,读取端再读,默认会阻塞当前读取的进程ssize_t s=read(fds[0],buffer,sizeof(buffer)-1);if(s>0)//s是read读取成功字节数{buffer[s]='\0';cout << "父进程的pid是:"<<getpid()<<" "<<buffer<<endl;}  else if(s==0)//如果子进程关闭写端,父进程将会输出“读完了”{//读到文件结尾cout<<"读完了"<<endl;break;}  }n=waitpid(id,nullptr,0);assert(n==id);close(fds[0]);//父进程读取fd用完记得关一下return 0;
}

2、匿名管道的读写情况

1、如果管道中没有数据,读取端进程再进行读取,会阻塞当前正在读取的进程;

2、如果写入端写满了,再写就会对该进程进行阻塞,需要等待对方对管道内数据进行读取;

3、如果写入进程关闭了写入fd,读取端将管道内的数据读完后read的返回值为0,和谐退出;

4、如果读关闭,操作系统会给写端发送13号信号SIGPIPE,终止写端。

3、管道的特征

1、只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道;

2、管道提供流式服务 ;

3、进程退出,管道释放,所以管道的生命周期随进程

4、内核会对管道操作进行同步与互斥

5、管道是半双工。需要双方通信时,需要建立起两个管道

4、基于匿名管道的进程池

#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
#define PROCESS_NUM 5
#define Make_Seed() srand((unsigned long)time(nullptr)^getpid()^0X55^rand()%1234)
typedef void(*func_t)();//函数指针类型
//子进程需要完成的任务
void downLodeTask()
{cout<<getpid()<<"下载任务"<<endl;sleep(1);
}
void ioTask()
{cout<<getpid()<<"IO任务"<<endl;sleep(1);
}
void flushTask()
{cout<<getpid()<<"刷新任务"<<endl;sleep(1);
}
//多进程代码
class sunEndPoint
{
public:sunEndPoint(pid_t subId,int writeFd):_subId(subId),_writeFd(writeFd){char namebuffer[1000];snprintf(namebuffer,sizeof(namebuffer),"process-%d[pid(%d)-fd(%d)]",num++,_subId,_writeFd);_name=namebuffer;}
public:string _name;pid_t _subId;//pidint _writeFd;//写fdstatic int num;
};
int sunEndPoint::num=0;
void loadTaskFunc(vector<func_t>* out)
{assert(out);out->push_back(downLodeTask);out->push_back(ioTask);out->push_back(flushTask);
}
int recvTask(int readFd)
{int code=0;ssize_t s=read(readFd,&code,sizeof(code));if(s==sizeof(code))//合法信息{return code;}else if(s<=0){return -1;}else return 0;
}
void createSubProcess(vector<sunEndPoint>* subs,vector<func_t>& funcMap)
{vector<int> deleteFd;//解决下一个子进程拷贝父进程读端的问题for(int i=0;i<PROCESS_NUM;++i){int fds[2];int n=pipe(fds);assert(n==0);(void)n;pid_t id=fork();if(id==0)//子进程{//关闭上一个文件的写端文件描述符for(int i=0;i<deleteFd.size();++i){close(deleteFd[i]);}//子进程,处理任务close(fds[1]);while(true){//1、获取命令码,如果父进程没有发送,子进程被阻塞int commandCode=recvTask(fds[0]);//2、完成任务if(commandCode>=0&&commandCode<funcMap.size()){funcMap[commandCode]();}else if(commandCode==-1){break;}}exit(0); }close(fds[0]);sunEndPoint sub(id,fds[1]);subs->push_back(sub);deleteFd.push_back(fds[1]);}
}
void sendTask(const sunEndPoint& process,int taskNum)
{cout<<"send task num"<<taskNum<<"send to->"<<process._name<<endl;int n=write(process._writeFd,&taskNum,sizeof(taskNum));assert(n==sizeof(int));//判断是否成功写入4个字节(void)n;
}
void loadBlanceContrl(const vector<sunEndPoint>& subs,const vector<func_t>& funcMap,int count)
{int processnum =subs.size();//子进程的个数int tasknum=funcMap.size();bool forever=(count==0?true:false);while(true){//选择一个子进程,从vector<sunEndPoint>选择一个indexint subIdx=rand()%processnum;//选择一个任务,从vector<func_t>选择一个indexint taskIdx=rand()%tasknum;//将任务发送给指定的子进程,将一个任务的下标发送给子进程sendTask(subs[subIdx],taskIdx);//taskIdx作为管道的大小4个字节sleep(1);if(!forever)//forever不为0{--count;if(count==0)break;}}//写端退出,读端将管道内数据读完后read返回0for(int i=0;i<processnum;++i){close(subs[i]._writeFd);//最晚被创建的子进程拥有早期创建的子进程的父进程的读端,所以这里其实是后创建的进程先关闭}
}
//回收子进程
void waitProcess(vector<sunEndPoint> processes)
{int processnum=processes.size();for(int i=0;i<processnum;++i){waitpid(processes[i]._subId,nullptr,0);cout<<"wait sub process success"<<processes[i]._subId<<endl;}
}
//父进程给子进程发布命令,父进程写,子进程读
int main()
{Make_Seed();//创建随机数//父进程创建子进程及和子进程通信的管道vector<func_t> funcMap;//vector<函数指针> funcMaploadTaskFunc(&funcMap);//加载任务vector<sunEndPoint> subs;//子进程集合createSubProcess(&subs,funcMap);//维护父子通信信道//这里的程序是父进程,用于控制子进程int taskCnt=9;//让子进程做9个任务loadBlanceContrl(subs,funcMap,taskCnt);//回收子进程信息waitProcess(subs);return 0;
}

四、命名管道(open打开相同文件实现,可用于非血缘关系的进程间通信)

1、命名管道的创建及删除

1.1命令行创建命名管道

mkfifo filename//filename是以p开头的管道文件

命名管道可用于无血缘关系的进程间通信,两个进程均打开同一路径下的同名文件(看到同一份资源),便能进行通信。命名管道大小为零的原因是数据并不会被刷新到磁盘中,它也是一个内存级文件。

1.2程序内创建及删除命名管道

创建命名管道:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);//路径,权限码
成功返回0,失败返回-1(失败错误码errno被设置)

删除命名管道: 

#include <unistd.h>
int unlink(const char *path);//路径
成功返回0,失败返回-1(失败错误码errno被设置)

1.3基于命名管道的用户端发送,服务端接收

1、comm.hpp

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define NAMED_PIPE "/tmp/mypipe"
//创建管道
bool createFifo(const std::string& path)
{umask(0);int n=mkfifo(path.c_str(),0600);//创建管道文件if(n==0)return true;else{std::cout<<"error:"<<errno<<"error string"<<strerror(errno)<<std::endl;return false;}
}
//销毁管道
void destroyFifo(const std::string& path)
{int n=unlink(NAMED_PIPE);assert(n==0);(void)n;
}

2、server.cc(先运行服务端)

#include "comm.hpp"
int main()
{bool r=createFifo(NAMED_PIPE);assert(r);(void)r;std::cout << "server begin" << std::endl;int rfd =open(NAMED_PIPE,O_RDONLY);//会等用户端打开管道文件,服务端才进行open服务端以只读的方式打开if(rfd==-1)//打开失败exit(1);//读取char buffer[1000];while(true){ssize_t s=read(rfd,buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;//将最后一个字符设置为'\0'std::cout<<"client->server:"<<buffer;}else if(s==0)//说明客户端一直没发数据{std::cout << "client quit, me too!" << std::endl;break;}else//读取出错{std::cout<<"err string"<<strerror(errno)<<std::endl;break;}}close(rfd);destroyFifo(NAMED_PIPE);return 0;
}

服务端在以只读的方式open管道文件前,会被阻塞,等待用户端open写入管道文件。(读写端均需要被打开)

3、client.cc

#include "comm.hpp"
int main()
{int wfd =open(NAMED_PIPE,O_WRONLY);//用户端以写的方式打开if(wfd==-1)//打开失败exit(1);char buffer[1000];while(true){std::cout<<"you can say:";fgets(buffer,sizeof(buffer),stdin);//fgets剩一个空间会被系统填充'\0',不用-1ssize_t s=write(wfd,buffer,strlen(buffer));assert(s==strlen(buffer));(void)s;}close(wfd);return 0;
}

五、System V共享内存

1、共享内存(物理内存块+相关属性)的原理

需要让不同进程看到同一块内存资源。用户使用操作系统提供的接口在物理内存中申请一块资源,通过进程的页表将这段物理空间映射至进程地址空间,进程将这段虚拟地址的起始地址返回给用户。通信结束后记得取消物理内存和虚拟内存的映射关系(去关联),并释放共享内存。

1、共享内存和malloc有点像,区别在于malloc出来的内存只能本进程知道这块空间的地址,共享内存是通过开辟一块物理空间,分别映射至通信进程的虚拟地址空间中。

2、共享内存是一种通信方式,所有想通信的进程都可以用,所以操作系统中可能会同时存在很多个共享内存。

2、共享内存相关命令

2.1查看共享内存(ipcs -m/-q/-s)

ipcs -m/-q/-s     //共享内存/消息队列/信号量数组

2.2删除共享内存(ipcrm -m shmnid)

ipcrm -m shmnid//使用shmid删除共享内存

3、创建/查看/删除/控制(删除)/关联共享内存

3.1形成key(ftok)

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);//路径/项目id
成功时,将返回key。失败返回-1(失败错误码errno被设置为系统调用出错)

通过传入相同的pathname和proj_id得到相同的key,从而找到同一块共享内存,实现进程间通信。

key通过shmget,设置进共享内存的属性中用来标识该共享内存在内核中的唯一性。key可以理解为一个个房间(内存块)的门牌号(编号)。

shmget的返回值:key类似fd:inode的关系。

3.2创建共享内存(shmget)

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);//标定唯一性(确认是哪块共享内存)/申请多大的内存空间/二进制标志位,见下图
成功时,将返回一个有效的共享内存标识符。(不同操作系统的数字下标不同,和文件的数字下标不兼容)
失败返回-1(失败错误码errno被设置)

3.3关联/卸载共享内存(shmat/shmdt)(关联类似malloc)

1、将共享内存与虚拟内存进行关联

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);//共享内存id,被映射的进程地址空间(给nullptr),给0默认可以读写
成功时,将返回共享内存的虚拟地址。失败返回-1(失败错误码errno被设置) 

2、将共享内存与虚拟内存去关联

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);//参数:shmat的返回值
成功时,将返回0。失败返回-1(失败错误码errno被设置)

3.4控制(主要用移除)共享内存(shmctl)

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//shmid(类似fd),传入系统设定的宏,shmid_ds数据结构
传入IPC_RMID移除共享内存成功时,将返回0。失败返回-1(失败错误码errno被设置)

4、利用共享内存进行进程间通信

1、comm.hpp

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATHNAME "."//当前路径(路径都行)
#define PROJ_ID 0X55//项目id也无要求
#define MAX_SIZE 4096
key_t getKey()
{key_t k=ftok(PATHNAME, PROJ_ID);if(k==-1){std::cout<<"ftok"<<errno<<":"<<strerror(errno)<<std::endl;exit(1);}return k;
}
int getShmHelper(key_t key,int flags)
{int shmid=shmget(key,MAX_SIZE,flags);if(shmid==-1)//创建共享内存失败{std::cerr<<"shmget"<<errno<<":"<<strerror(errno)<<std::endl;exit(2);}return shmid;//返回共享内存标识符
}
int getShm(key_t key)//创建||获取共享内存
{return getShmHelper(key,IPC_CREAT);//传0也行
}
int createShm(key_t key)//必定创建共享内存
{return getShmHelper(key,IPC_CREAT|IPC_EXCL|0600);//生成一个全新的共享内存
}
void* attachShm(int shmid)//让共享内存与虚拟内存建立联系
{void* memstart=shmat(shmid,nullptr,0);if((long long)memstart==-1L){std::cerr<<"shmat"<<errno<<":"<<strerror<<std::endl;exit(3);}return memstart;
} 
void detchShm(void* memStart)//去关联
{if(shmdt(memStart)==-1){std::cerr<<"shmdt"<<errno<<":"<<strerror<<std::endl;exit(4);}
}   
void delShm(int shmid)//删除共享内存
{if(shmctl(shmid,IPC_RMID,nullptr)==-1){std::cerr<<"shmctl"<<errno<<":"<<strerror<<std::endl;}
}

2、shm_server.cc

#include "comm.hpp"
int main()
{key_t k=getKey();printf("0X%x\n",k);int shmid=createShm(k);char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系printf("memStart address:%p\n",memStart); //通信接收代码while(true){printf("client say:%s\n",memStart); sleep(1);//调用用户级结构体struct shmid_ds ds;//创建结构体对象dsshmctl(shmid,IPC_STAT,&ds);//获取ds对象的状态printf("获取属性:%d,pid:%d,myself:%d,key:%d\n",ds.shm_segsz,getpid(),ds.shm_cpid,ds.shm_perm.__key);}detchShm(memStart);//去关联sleep(10);delShm(shmid);//删除共享内存,client和server都能删除共享内存,尽量谁创建谁删return 0;
}

2、shm_client.cc

#include "comm.hpp"
int main()
{key_t k=getKey();printf("0X%x\n",k);int shmid=getShm(k);//获取共享内存sleep(5);char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系printf("memStart address:%p\n",memStart); //通信传输代码const char* massage="I am client";pid_t id=getpid();int cnt=0;//发送计数while(true){snprintf(memStart,MAX_SIZE,"%s[%d]:%d\n",massage,getpid,++cnt);sleep(1);}detchShm(memStart);//去关联return 0;
}

5、共享内存的优缺点

5.1共享内存的优点

共享内存是所有进程间通信中速度最快的。(无需缓冲区,能大大减少通信数据的拷贝次数)

5.2共享内存的缺点

如果服务端读取速度较快,用户端发送数据较慢,就会产生同一段消息被服务端读取多遍。共享内存是不进行同步和互斥的,没有对数据进行任何保护。

5.3共享内存的特点

上图表明共享内存的生命周期是随操作系统的,进程的退出不会销毁共享内存。这是System V资源的特征。

5.4共享内存大小的建议

因为系统分配共享内存是以4KB为基本单位,一般建议申请共享内存的大小为4KB的整数倍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73945.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【华为OD机试模拟题】用 C++ 实现 - 密室逃生游戏(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

独家 | 6个Python数据科学库正在狂飙,你一定要学来提升文化素养

作者&#xff1a;Bex T翻译&#xff1a;wwl 校对&#xff1a;张睿毅本文约3200字&#xff0c;建议阅读8分钟 计算类数据科学库&#xff0c;已经不再局限在Pandas、NumPy、Scikit-learn之内了&#xff01;动机2023年的开始&#xff0c;自然需要探索数据科学和机器学习的新趋势。…

【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

Web Spider Ast-Hook 浏览器内存漫游-数据检索

文章目录一、资源下载二、通过npm安装anyproxy模块三、anyproxy的介绍以及基本使用1. anyproxy的功能介绍2. anyproxy的基本使用四、给浏览器挂代理五、实操极验demo案例总结提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、资源下载 Github&#x…

【华为OD机试模拟题】用 C++ 实现 - 符合条件的子串长度(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【华为OD机试模拟题】用 C++ 实现 - 矩阵最值(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【华为OD机试模拟题】用 C++ 实现 - 找数字(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

MIND——Modality independent neighbourhood descriptor 模态无关邻域描述符

参考&#xff1a;https://blog.mbedded.ninja/programming/signal-processing/image-processing/image-registration/modality-independent-neighbourhood-descriptor-mind/《MIND: Modality independent neighbourhood descriptor for multi-modal deformable registration》论…

Elasticsearch7.8.0版本进阶——文档分析 分析器

目录一、文档分析过程二、分析器三、内置分析器3.1、标准分析器3.2、简单分析器3.3、空格分析器3.4、语言分析器四、分析器使用场景五、分析器的测试示例一、文档分析过程 将一块文本分成适合于倒排索引的独立的词条。将这些词条统一化为标准格式以提高它们的“可搜索性”&…

JDBC-

文章目录JDBC1&#xff0c;JDBC概述1.1 JDBC概念1.2 JDBC本质1.3 JDBC好处2&#xff0c;JDBC快速入门2.1 编写代码步骤2.2 具体操作3&#xff0c;JDBC API详解3.1 DriverManager3.2 Connection &#xff08;事务归我管&#xff09;3.2.1 获取执行对象3.2.2 事务管理3.3 Stateme…

12.STM32系统定时器-SysTick

目录 1.系统定时器-SysTick 2.SysTick定时时间的计算 3.SysTick结构体 4.SysTick固件库函数 5.SysTick中断优先级 1.系统定时器-SysTick SysTick:24位系统定时器&#xff0c;只能递减&#xff0c;存在于内核嵌套在NVIC中。所有的Cortex-M中都有这个系统定时器。 重装载值…

interrupt多线程设计模式

1. 两阶段终止-interrupt Two Phase Termination 在一个线程T1中如何“优雅”终止线程T2&#xff1f;这里的【优雅】指的是给T2一个料理后事的机会。 错误思路 ● 使用线程对象的stop()方法停止线程&#xff08;强制杀死&#xff09; —— stop&#xff08;&#xff09;方法…

会声会影2023电脑版下载及系统配置要求

平时大家可能会经常听到有人说会声会影&#xff0c;但是很多人都不知道这是什么软件。其实听它的名字就知道这是一款和声音、影像有关系的软件。下面&#xff0c;小编就来给大家具体介绍一下这款软件吧。 会声会影是一套操作简单的DV、HDV影片剪辑软件。会声会影不仅完全符合家…

农业科技发展所带来的好处:提高农作物产量,提高农民收入

农业科技发展所带来的好处&#xff1a;&#xff1a;&#xff08;1&#xff09;育种技术&#xff1a;通过育种技术&#xff0c;科学家可以在农作物基因中挑选和改变一些特定的性状&#xff0c;例如增加产量、改善耐旱性和抗病性等等&#xff0c;从而提高农作物产量。&#xff08…

el-cascader 级联选择器懒加载的使用及回显 + 点击任意一级都能返回

需要实现的需求 数据渲染使用懒加载点击任意一级都可返回&#xff0c;不需要一直点到最后一级编辑或者查看功能&#xff0c;回显之前选择的数据 实例解析 dom 元素 <el-cascaderv-model"value":options"options":props"props":key"n…

Linux内核段页式内存管理技术

一、概述 1.虚拟地址空间 内存是通过指针寻址的&#xff0c;因而CPU的字长决定了CPU所能管理的地址空间的大小&#xff0c;该地址空间就被称为虚拟地址空间&#xff0c;因此32位CPU的虚拟地址空间大小为4G&#xff0c;这和实际的物理内存数量无关。 Linux内核将虚拟地址空间分…

五种IO模型以及select多路转接IO模型

目录 一、典型IO模型 1.1 阻塞IO 1.2 非阻塞IO 1.3 信号驱动I0 1.4 IO多路转接 1.5 异步IO 多路转接的作用和意义 二、多路转接IO模型&#xff08;select&#xff09; 2.1 接口 2.2 接口当中的事件集合&#xff1a; fd_set 2.2 select使用事件集合&#xff08;位图&am…

174万亿采购,奔向数字化

采购不单纯发生在外部&#xff0c;更发生在内部&#xff0c;只有两者同时进行&#xff0c;才能完成采购中心从成本到利润中心角色的转变。 作者|斗斗 编辑|皮爷 出品|产业家 数字化&#xff0c;让很多企业业务流程发生了质变。 《2022数字化采购发展报告》显示&#x…

破解票房之谜:为何高票房电影绕不过“猫眼们”?

如此火爆的春节档很多&#xff0c;如此毁誉参半的春节档鲜有。2023开年&#xff0c;集齐张艺谋、沈腾的《满江红》&#xff0c;以及有票房前作打底的《流浪地球2》接连两部春节档电影票房进入前十&#xff0c;为有些颓靡的中国电影市场注入了一针“强心剂”。与票房同样热闹起来…

无重叠区间-力扣435-java贪心策略

一、题目描述给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。示例 1:输入: intervals [[1,2],[2,3],[3,4],[1,3]]输出: 1解释: 移除 [1,3] 后&#xff0c;剩下的区间没有重叠。…