Linux进程间通信之System V

news2025/1/21 9:29:18

目录

认识system V:

system V共享内存:

 共享内存的基本原理:

共享内存的数据结构:

共享内存的建立与释放:

共享内存的建立:

 共享内存的释放:

共享内存的关联: 

 共享内存的去关联:

用共享内存实现serve&client通信:

 system V消息队列:

消息队列基本原理:

消息队列数据结构:

消息队列的创建:

消息队列的释放:

向消息队列发送数据:

从消息队列获取数据:

system信号量:

信号量相关概念:

信号量数据结构:

信号量集的创建:

 信号量集的删除:

 信号量集的操作:

进程互斥 


认识system V:

对于进程间通信,想必管道大家再熟悉不过了,对于管道这种通信方式,其实是对底层代码的一种复用,linux工程师借助类似文件缓冲区的内存空间实现了管道,其实也算偷了一个小懒,随着linux的发展,linux正式推出了System V来专门进行进程间通信,它和管道的本质都是一样的,都是让不同的进程看到同一份资源。

system V通信的3种通信方式:

1.system V共享内存 ()

2.system V消息队列 ()

3.system V信号量 ()

上述中的共享内存和消息队列主要用于传输数据,而信号量则是用于保证进程间的同步与互斥,虽然看起来信号量和通信没关联,但其实它也属于通信的范畴。

system V共享内存:

 共享内存的基本原理:

之前说的到了通信的原理都是让不同的进程看到同一份资源,共享内存让进程看到同一份资源的方法就是,在物理内存中申请一块空间,名为共享内存,然后让这块空间与需要通信的进程的页表建立映射,再在进程的虚拟地址的栈区和堆区中间的共享区,开辟一段空间,将该空间的地址页表对应的位置,这样虚拟地址就和物理地址建立了联系,让不同的进程看到了同一份资源。

注意:这里说的开辟物理空间和建立页表映射关系,都是由操作系统来完成。

共享内存的数据结构:

系统中可能不止一对进程需要通信,一块共享内存只能支持两个进程通信,所以操作系统是支持申请多个共享内存的,而多个共享内存被操作系统管理,所以操作系统中一定有管理共享内存的内核数据结构:

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

当我们申请一块共享内存,system V为了能让不同的进程看到这块共享内存,每个共享内存申请时都会有一个key值,用于系统标志这块共享内存的唯一性。

可以看到上面共享内存数据结构中,第一个成员是shm_permshm_perm是一个ipc_perm类型的结构体变量,ipc_perm中存放了每个共享内存的key,ipc_perm的结构如下:

struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

共享内存的建立与释放:

共享内存的建立大致为以下两步:

1.在物理空间中开辟一块共享内存空间。

2.将该物理空间与进程虚拟地址空间通过页表建立映射关系。(挂载)

共享内存的释放大致为以下两步: 

1.将该物理空间和进程虚拟地址空间取关联,取消页表映射。(去挂载)

2.释放共享空间,将物理内存还给操作系统。

共享内存的建立:

共享内存的建立需要使用smhget函数:

smhget参数说明:

key:表示待创建共享内存在系统的唯一标识。

size:表示想要申请的共享内存的大小。(建议4096的整数倍)

shmflg:表示创建共享内存的方式。

smhget返回值说明:

若创建成功则返回共享内存的描述符smhid(用户层的,和key不同) 

若创建失败则返回 -1

注意key值是需要我们自己传入的,我们可以想传什么就传什么,但key不可重复,所以建议使用ftok函数来取到合适的key:

 注意:ftok函数是将一个路径pathname和一个proj_id通过一个特定的函数转换成key值。

传入shmget函数的第三个参数shmflg,常用的组合方式有以下两种: 

组合方式作用
IPC_CREAT如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄
IPC_CREAT|IPC_EXCL如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则出错返回

这两种奇怪的区分到底有什么用呢?

若是第一种方式拿到了一个描述符,则说明该共享内存一定是旧的。

若是第二种方式拿到了一个描述符,则说明该共享内存一定是新的。

所以我们用第二种组合方式来创建共享内存,用第一种组合方式来找到一个共享内存。

共享内存创建好后,我们是可以通过ipcs命令来进行查询的:

 ipcs命令选项介绍:

  • -q:列出消息队列相关信息。
  • -m:列出共享内存相关信息。
  • -s:列出信号量相关信息。

不加选项默认全部列出:

 图中每列信息如下:

标题含义
key系统区别各个共享内存的唯一标识
shmid共享内存的用户层id(句柄)
owner共享内存的拥有者
perms共享内存的权限
bytes共享内存的大小
nattch关联共享内存的进程数
status共享内存的状态

现在我们编写一个简单的程序来创建一个共享内存,并打印出它的key和描述符:

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

const char* pathname = "/home/sxk/linux2/24_6_6";
int proj_id = 0x66;

int main()
{
    //得出key
    key_t key = ftok(pathname,proj_id);
    if(key < 0)
    {
        perror("ftok");
    }
    //创建共享内存
    int shmid = shmget(key,4096,IPC_CREAT);
    if(shmid < 0)
    {
        perror("shmget");
    }

    //打印出共享内存的key和shmid
    printf("key:   %x\n",key);
    printf("shmid: %d\n",shmid);
    sleep(10);
    return 0;
}

运行结果:

 共享内存的释放:

先介绍一个共享内存的重要特性:

共享内存不随程序的结束而释放。

 所以,当我们的程序结束后共享内存仍然存在:

如果想要释放这个共享内存有两种方法:

1.使用 ipcrm -m 描述符  指令来删除指定的共享内存

2.在代码中使用shmctl函数:

shmctl函数参数选项介绍: 

  • 第一个参数shmid,表示所控制共享内存的用户级标识符。
  • 第二个参数cmd,表示具体的控制动作。
  • 第三个参数buf,用于获取或设置所控制共享内存的数据结构

shmctl函数的返回值说明:

  • shmctl调用成功,返回0。
  • shmctl调用失败,返回-1。

第二个参数cmd常用的几个选项如下:

选项作用
IPC_STAT获取共享内存的当前关联值,此时参数buf作为输出型参数
IPC_SET在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID删除共享内存段

修改之前的代码,创建共享内存2秒后删除共享内存:

共享内存的关联: 

 共享内存在物理空间创建好后,还需将物理内存的地址与进程的虚拟地址空间中的共享区的地址,通过页表映射建立联系,这样之后进程才能访问这片共享内存。

通过shmat函数来建立映射关系

shmat函数的参数说明:

  • 第一个参数shmid,表示待关联共享内存的用户级标识符。
  • 第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。
  • 第三个参数shmflg,表示关联共享内存时设置的某些属性。

shmat函数的返回值说明:

  • shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。
  • shmat调用失败,返回(void*)-1。

其中,作为shmat函数的第三个参数传入的常用的选项有以下三个:

选项作用
SHM_RDONLY关联共享内存后只进行读取操作
SHM_RND若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
0默认为读写权限

 共享内存的去关联:

使用shmdt函数来去关联:

shmat函数参数介绍:

  • shmaddr:表示需要去关联的共享内存

shmat函数的返回值

  • 若去关联成功, 则返回0
  • 若去关联失败, 则返回-1

用共享内存实现serve&client通信:

serve端负责创建共享内存,并收消息,client,负责发消息。

serve.cc:

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

const char* pathname = "/home/sxk/linux2/24_6_6";
int proj_id = 0x66;

int main()
{
    //得出key
    key_t key = ftok(pathname,proj_id);
    if(key < 0)
    {
        perror("ftok");
    }
    //创建共享内存
    int shmid = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);
    if(shmid < 0)
    {
        perror("shmget");
    }
    //打印出共享内存的key和shmid
    printf("key:   %x\n",key);
    printf("shmid: %d\n",shmid);
    sleep(5);
    //与共享内存关联
    char* msg = (char*)shmat(shmid,NULL,0);
    if(msg == (void*)-1)
    {
        perror("shmat");
    }
    //开始读消息
    std::cout<<"serve begin read msg :"<<std::endl;
    while(1)
    {
        std::cout<<msg<<std::endl;
        sleep(1);
    }
    //读完,去关联
    int n = shmdt(msg);
    if(n < 0)
    {
        perror("shmdt");
    }
    //释放共享内存
    int t = shmctl(shmid,IPC_RMID,NULL);
    if(t < 0)
    {
        perror("shmctl");
    }
    return 0;
}

client.cc:

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

const char* pathname = "/home/sxk/linux2/24_6_6";
int proj_id = 0x66;

int main()
{
    //获取key
    key_t key = ftok(pathname,proj_id);
    if(key < 0)
    {
        perror("ftok");
    }
    //获取共享内存
    int shmid = shmget(key,4096,IPC_CREAT);
    if(shmid < 0)
    {   
        perror("shmget");
    }
    //与共享内存关联指定shmid,不指定地址起始位置,读写权限
    char* msg = (char*)shmat(shmid,NULL,0);
    if(msg == (void*)-1)
    {  
        perror("shmat");
    }
    //开始发送消息
    char a = 'A';
    int i = 0;
    while(a < 'Z')
    {
        msg[i] = a + i;
        i++;
        sleep(1);
    }
    //发送完毕,去关联
    int t = shmdt(msg);
    if(t < 0 )
    {
        perror("shmdt");
    }
    return 0;
}

运行结果:

 system V消息队列:

消息队列基本原理:

消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块。

总结一下:

  1. 消息队列提供了一个从一个进程向另一个进程发送数据块的方法。
  2. 每个数据块都被认为是有一个类型的,接收者进程接收的数据块可以有不同的类型值。
  3. 和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。

消息队列数据结构:

当然,系统当中也可能会存在大量的消息队列,系统一定也要为消息队列维护相关的内核数据结构。

消息队列的数据结构如下:

struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;      /* first message on queue,unused  */
	struct msg *msg_last;       /* last message in queue,unused */
	__kernel_time_t msg_stime;  /* last msgsnd time */
	__kernel_time_t msg_rtime;  /* last msgrcv time */
	__kernel_time_t msg_ctime;  /* last change time */
	unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes; /* ditto */
	unsigned short msg_cbytes;  /* current number of bytes on queue */
	unsigned short msg_qnum;    /* number of messages in queue */
	unsigned short msg_qbytes;  /* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

可以看到消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

消息队列的创建:

创建消息队列我们需要用msgget函数:

msgget函数参数介绍:

key:表示带创建消息队列在系统的唯一标识。(跟共享内存差不多)

msgflg:和shmget的第三个参数一样。

msgget函数返回值介绍:

创建消息队列成功则返回该消息队列的描述符。(用户级)

消息队列的释放:

释放消息队列我们需要用msgctl函数:

msgctl和shmctl用法基本相同。

向消息队列发送数据:

向消息队列发送数据我们需要用msgsnd函数:

msgsnd函数的参数说明:

  • 第一个参数msqid,表示消息队列的用户级标识符。
  • 第二个参数msgp,表示待发送的数据块。
  • 第三个参数msgsz,表示所发送数据块的大小
  • 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。

msgsnd函数的返回值说明:

  • msgsnd调用成功,返回0。
  • msgsnd调用失败,返回-1。

其中msgsnd函数的第二个参数必须为以下结构:

struct msgbuf{
	long mtype;       /* message type, must be > 0 */
	char mtext[1];    /* message data */
};

注意: 该结构当中的第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。

从消息队列获取数据:

从消息队列获取数据我们需要用msgrcv函数:

msgrcv函数的参数说明:

  • 第一个参数msqid,表示消息队列的用户级标识符。
  • 第二个参数msgp,表示获取到的数据块,是一个输出型参数。
  • 第三个参数msgsz,表示要获取数据块的大小
  • 第四个参数msgtyp,表示要接收数据块的类型。

msgrcv函数的返回值说明:

  • msgsnd调用成功,返回实际获取到mtext数组中的字节数。
  • msgsnd调用失败,返回-1。

system信号量:

信号量相关概念:

由于进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系叫做进程互斥。


系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。

在进程中涉及到临界资源的程序段叫临界区。

IPC资源必须删除,否则不会自动删除,因为system V IPC的生命周期随内核。

信号量数据结构:

在系统当中也为信号量维护了相关的内核数据结构:

struct semid_ds {
	struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
	__kernel_time_t sem_otime;      /* last semop time */
	__kernel_time_t sem_ctime;      /* last change time */
	struct sem  *sem_base;      /* ptr to first semaphore in array */
	struct sem_queue *sem_pending;      /* pending operations to be processed */
	struct sem_queue **sem_pending_last;    /* last pending operation */
	struct sem_undo *undo;          /* undo requests on this array */
	unsigned short  sem_nsems;      /* no. of semaphores in array */
};

信号量数据结构的第一个成员也是ipc_perm类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

信号量集的创建:

创建信号量集我们需要用semget函数: 

创建信号量集也需要使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。

semget函数的第二个参数nsems,表示创建信号量的个数。

semget函数的第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。

信号量集创建成功时,semget函数返回的一个有效的信号量集标识符(用户层标识符)。
信号量集的删除

 信号量集的删除:

 删除信号量集我们需要用semctl函数:

 信号量集的操作:

 对信号量集进行操作我们需要用semop函数:

进程互斥 

进程间通信通过共享资源来实现,这虽然解决了通信的问题,但是也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题。

保护临界资源的本质是保护临界区,我们把进程代码中访问临界资源的代码称之为临界区,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量。

信号量本质是一个计数器,在二元信号量中,信号量的个数为1(相当于将临界资源看成一整块),二元信号量本质解决了临界资源的互斥问题,以下面的伪代码进行解释: 

 根据以上代码,当进程A申请访问共享内存资源时,如果此时sem为1(sem代表当前信号量个数),则进程A申请资源成功,此时需要将sem减减,然后进程A就可以对共享内存进行一系列操作,但是在进程A在访问共享内存时,若是进程B申请访问该共享内存资源,此时sem就为0了,那么这时进程B会被挂起,直到进程A访问共享内存结束后将sem加加,此时才会将进程B唤起,然后进程B再对该共享内存进行访问操作。

在这种情况下,无论什么时候都只会有一个进程在对同一份共享内存进行访问操作,也就解决了临界资源的互斥问题。

实际上,代码中计数器sem减减的操作就叫做P操作,而计数器加加的操作就叫做V操作,P操作就是申请信号量,而V操作就是释放信号量。

感谢阅读!

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:腾讯云自媒体同步曝光计划 - 腾讯云开发者社区-腾讯云

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动开发之 input 子系统

1.input 子系统介绍 input 就是输入的意思&#xff0c;input 子系统就是管理输入的子系统&#xff0c;和 pinctrl、gpio 子系统 一样&#xff0c;都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等 等这些都属于输入设备&#xff0c;不同的输入…

区块链的基本原理和优势

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

数据结构_手撕八大排序(计数,快排,归并,堆排,希尔,选择,插入,冒泡)

✨✨所属专栏&#xff1a;数据结构✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序…

Docker:认识镜像仓库及其命令

文章目录 Docker Registry什么是Docker Registry 镜像仓库工作机制使用流程实际使用方法仓库的拉取机制 常用的镜像仓库---DockerHub什么是DockerHub私有仓库 镜像仓库命令docker logindocker pulldocker pushdocker searchdocker logout Docker Registry 什么是Docker Regist…

[线程与网络] 网络编程与通信原理(六):深入理解应用层http与https协议(网络编程与通信原理完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

【java】速度搭建一个springboot项目

使用软件&#xff1a;IDEA&#xff0c;mysql 使用框架&#xff1a;springboot mybatis-plus druid 坑点 使用IDEA搭建一个springboot项目的时候&#xff0c;需要考虑一下IDEA版本支持的JDK版本以及maven版本。否则再构建项目&#xff0c;引入pom的时候就会报错。 需要检查…

C++全栈聊天项目(21) 滚动聊天布局设计

滚动聊天布局设计 我们的聊天布局如下图 最外层的是一个chatview&#xff08;黑色&#xff09;&#xff0c; chatview内部在添加一个MainLayout&#xff08;蓝色&#xff09;&#xff0c;MainLayout内部添加一个scrollarea(红色)&#xff0c;scrollarea内部包含一个widget&…

Linux shell编程学习笔记57:lshw命令 获取cpu设备信息

0 前言 在Linux中&#xff0c;获取cpu信息的命令很多&#xff0c;除了我们已经研究的 cat /proc/cpuinfo、lscpu、nproc、hwinfo --cpu 命令&#xff0c;还有 lshw命令。 1 lshw命令的功能 lshw命令源自英文list hardware&#xff0c;即列出系统的硬件信息&#xff0c;这些硬…

UI 自动化分布式测试 -Docker Selenium Grid

分布式测试Selenium Grid 对于大型项目或者有大量测试用例的项目,单机的测试环境往往无法快速完成所有测试用例的执行,此时自动化测试执行效率将会成为最大的瓶颈,Selenium Grid 可以通过多机的分布式架构允许测试用例并行运行,大大缩短了测试时间。 Selenium Grid 提供了多…

限时限量!6.18云服务器大促盘点,错过一次,再等一年!

随着云计算技术的飞速发展&#xff0c;云服务器已成为企业和个人构建和扩展在线业务的首选平台。特别是在大型促销活动如618年中大促期间&#xff0c;云服务提供商纷纷推出极具吸引力的优惠&#xff0c;以降低用户上云的门槛。以下是对当前市场上几个主流云服务提供商的优惠活动…

JavaScript入门宝典:核心知识全攻略(下)

文章目录 前言一、获取标签元素二、操作标签元素属性1. 属性的操作2. innerHTML 三、数组及操作方法1. 数组的定义2. 数组的操作 四、循环语句五、字符串拼接六、定时器1. 定时器的使用3. 清除定时器 七、ajax1. ajax的介绍2. ajax的使用 前言 JavaScript是前端开发不可或缺的技…

C++| 一维线性插值、imadjust函数

前言&#xff1a;最近要从Matlab代码改C代码&#xff0c;不能直接用Matlab生成的C代码&#xff0c;因为需要嵌入到已有项目中。Matlab本身有很多很方便的数学公式&#xff0c;但是在C里没有相关的库的话&#xff0c;需要自己实现。 一维线性插值、imadjust函数 一维线性插值原理…

常见八大排序(纯C语言版)

目录 基本排序 一.冒泡排序 二.选择排序 三.插入排序 进阶排序&#xff08;递归实现&#xff09; 一.快排hoare排序 1.单趟排序 快排步凑 快排的优化 &#xff08;1&#xff09;三数取中 &#xff08;2&#xff09;小区间优化 二.前后指针法(递归实现) 三.快排的非…

【爬虫】使用Python爬取百度学术页面的标题、作者、摘要和关键词

目录 安装所需库编写爬虫代码解释运行脚本结果 在本文中&#xff0c;我将介绍如何使用Python编写一个网络爬虫&#xff0c;从百度学术页面提取研究论文的标题、作者、摘要和关键词。我们将使用 requests和 BeautifulSoup库来实现这一目标。 安装所需库 首先&#xff0c;确保…

力扣hot100:155. 最小栈(栈,辅助栈存储相关信息)

LeetCode&#xff1a;155. 最小栈 1、尝试单调栈 看到这题说&#xff0c;要常数时间内检索最小元素的栈&#xff0c;想到了单调栈&#xff0c;递增单调栈确实能维护最小值&#xff0c;但是这个最小值是存在一定意义的&#xff0c;即如果后面出现了最小值&#xff0c;那么前面…

PostgreSQL基础(十):PostgreSQL的并发问题

文章目录 PostgreSQL的并发问题 一、事务的隔离级别 二、MVCC PostgreSQL的并发问题 一、事务的隔离级别 在不考虑隔离性的前提下&#xff0c;事务的并发可能会出现的问题&#xff1a; 脏读&#xff1a;读到了其他事务未提交的数据。&#xff08;必须避免这种情况&#xf…

【Java】解决Java报错:NumberFormatException

文章目录 引言1. 错误详解2. 常见的出错场景2.1 字符串包含非数字字符2.2 空字符串或 null 字符串2.3 数值超出范围 3. 解决方案3.1 验证字符串格式3.2 使用异常处理3.3 处理空字符串和 null 4. 预防措施4.1 数据验证4.2 编写防御性代码4.3 单元测试 结语 引言 在Java编程中&a…

【百万字详解Redis】集群

文章目录 一、集群模式概述1.1、什么是集群模式1.2、集群模式特点1.3、集群工作方式 二、集群模式的搭建2.1、搭建前的准备2.2、修改集群配置2.3、启动redis服务2.4、创建集群2.5、查看redis服务状态2.6、进入一个节点2.7、测试操作 三、集群操作3.1、主从切换3.2、从节点操作3…

微软 Windows 10 22H2 发布可选更新 19045.4474,修复窗口显示问题等

微软今天面向 Windows 10 22H2 版本&#xff0c;发布了 KB5037849 非安全可选更新&#xff0c;用户安装后版本号升至 Build 19045.4474。 IT之家 5 月 30 日消息&#xff0c;微软今天面向 Windows 10 22H2 版本&#xff0c;发布了 KB5037849 非安全可选更新&#xff0c;用户安…

搭建 Langchain-Chatchat 详细过程

前言 本文参考官网和其他多方教程&#xff0c;将搭建 Langchain-Chatchat 的详细步骤进行了整理&#xff0c;供大家参考。 我的硬件 4090 显卡win10 专业版本 搭建环境使用 chatglm2-6b 模型 1. 创建虚拟环境 chatchat &#xff0c;python 3.9 以上 conda create -n chat…