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

【并发程序设计】15.信号灯(信号量)

15.信号灯(信号量)

Linux中的信号灯即信号量是一种用于进程间同步或互斥的机制,它主要用于控制对共享资源的访问。

在Linux系统中,信号灯作为一种进程间通信(IPC)的方式,与其他如管道、FIFO或共享内存等IPC方式不同,信号灯主要用于同步或互斥控制,以确保多个进程之间能够协调地访问共享资源。信号灯可以看作是内存中的一个标志,进程可以通过检查信号灯的状态来决定是否可以安全地访问某些共享资源

要点

  1. 创建和操作:要在Linux中使用信号灯,首先需要创建一个信号灯,并指定其初始值。对于二值信号灯,这个初始值通常是1,表示资源一开始是可用的。
  2. 等待和释放:进程可以执行等待操作来测试信号灯的值,如果信号灯的值大于0,则进程可以继续执行并将信号灯的值减1;如果值为0,则进程必须等待直到信号灯的值变为正数。
  3. 共享资源控制:信号灯提供了一种机制,使得进程能够根据信号灯的状态来判断是否可以访问某些共享资源。这类似于互斥锁,确保了在任何时候只有一个进程可以访问临界区。
  4. 进程间通信:除了同步和互斥,信号灯还可以作为进程间通信的一种方式。它们存在于内核空间,与共享内存和消息队列一起构成了Linux中主要的IPC通信方式。
  5. 有名信号灯: 可以通过路径名在进程间共享,因此不同进程可以通过已知的路径名来访问同一个有名信号灯。 适用于需要在不同进程间进行同步的场景。
  6. 无名信号灯: 只能存在于内存中,因此使用无名信号灯的进程必须能够访问相同的内存区域。通过共享内存的方式创建,不依赖于文件系统中的路径名。适用于单进程内多线程间的同步或在已经映射相同内存内容的多个进程之间的同步。
  7. System V信号灯:Linux支持System V的信号灯,这是一种传统的信号灯实现,用于在同一系统内的进程间进行同步和互斥。

互斥和同步 是信号灯通常用于解决并发中的两个主要问题:

  1. 互斥:确保当一个进程使用共享资源时,其他进程不能同时访问该资源。例如,打印设备只能由一个进程使用,其他尝试访问打印设备的进程必须等待,直到当前进程完成打印任务。

  2. 同步:确保进程间的执行顺序符合特定的依赖关系。例如,一个进程生成数据,另一个进程消费这些数据,消费者进程需要等待生产者进程生成数据后才能继续执行。

PV操作

信号灯的工作原理基于PV操作,其中P操作用于请求资源(减少信号量的值),而V操作用于释放资源(增加信号量的值)。

  • P操作“proberen”(尝试):如果信号量的值为正,则将其减一,允许进程继续执行。如果信号量的值为0或负,则进程被阻塞,直到信号量的值变为正数。

  • V操作“verhogen”(释放):将信号量的值加一,如果有其他进程因等待该信号量而被阻塞,则其中一个进程会被唤醒。

POSIX是一组用于确保操作系统间可移植性的IEEE标准,主要针对Unix系统。POSIX标准为操作系统提供了一套共通的规则,使得软件开发更加高效,同时也让用户能够在不同的系统之间无缝地迁移和运行应用程序。

有名信号灯

用到的函数主要有:

  1. sem_open 有名信号灯打开
  2. sem_close 有名信号灯关闭
  3. sem_unlink 有名信号灯的删除
  4. sem_wait 信号灯P操作,申请资源
  5. sem_post 信号灯V操作,释放资源

sem_open 函数

  1. 原型

    #include <fcntl.h>
    #include <sys/stat.h>
    #include <semaphore.h>
    sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
    
  2. 功能:创建或打开一个有名信号量(named semaphore)的函数

  3. 参数

    • name:信号量的名称,用于标识信号量。
    • oflag:打开选项,可以是以下值的组合:
      • O_CREAT:如果信号量不存在,则创建一个新的信号量。
      • O_EXCL:与O_CREAT一起使用,表示如果信号量已存在,则返回错误。
      • O_RDWR:允许对信号量进行读写操作。
    • mode:设置信号量的权限,通常设置为0644。
    • value:信号量的初始值。
  4. 返回值

    • 成功时,返回一个指向信号量的指针。
    • 失败时,返回SEM_FAILURE(通常是NULL)。

sem_close 函数

  1. 原型

    #include <semaphore.h>
    int sem_close(sem_t *sem);
    
  2. 功能:用于关闭一个信号量的函数

  3. 参数

    • sem:指向要关闭的信号量的指针
  4. 返回值

    • 成功时,返回0。
    • 失败时,返回-1,并设置errno。

sem_unlink 函数

  1. 原型

    #include <semaphore.h>
    int sem_unlink(const char *name);
    
  2. 功能:删除一个命名信号量

  3. 参数

    • name:要删除的信号量的名称
  4. 返回值

    • 成功时,返回0
    • 失败时,返回-1,并设置errno

sem_wait 函数

  1. 原型

    #include <semaphore.h>
    int sem_wait(sem_t *sem);
    
  2. 功能:”P操作“等待一个信号量。当信号量的值大于0时,该函数会将信号量的值减1,并立即返回。如果信号量的值为0,则该函数会阻塞当前线程,直到信号量的值大于0为止。

  3. 参数

    • sem:指向要等待的信号量的指针。
  4. 返回值

    • 成功时,返回0。
    • 失败时,返回-1,并设置errno。

sem_post 函数

  1. 原型

    #include <semaphore.h>
    int sem_post(sem_t *sem);
    
  2. 功能:”V操作“增加一个信号量的值。当信号量的值大于0时,如果有其他线程正在等待该信号量,则sem_post函数会唤醒其中一个等待的线程。如果没有线程在等待,那么信号量的值简单地增加,它与sem_wait函数相对应.

  3. 参数

    • sem:指向要操作的信号量的指针。
  4. 返回值

    • 成功时,返回0。
    • 失败时,返回-1,并设置errno。

示例-有名信号灯使用

使用信号量和共享内存实现的简单进程间通信(IPC)示例

一个简单的命令行界面,用户可以在命令行中输入字符串,并将其存储到共享内存中。通过信号量来实现进程间的同步,确保数据的读写操作不会发生冲突

tes_semw.c 写程序

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>void delsemfile(int sig)
{
sem_unlink("mysem_w");  // 删除名为"mysem_w"的信号量
exit(0);                // 退出程序
}int main()
{
sem_t *sem_r,*sem_w;     // 定义两个信号量指针,分别用于读和写操作
key_t key;               // 定义键值,用于生成共享内存标识符
int shmid;               // 定义共享内存标识符
char *shmaddr;           // 定义共享内存地址指针struct sigaction act;    // 定义信号处理结构体
act.sa_handler = delsemfile;  // 设置信号处理函数为delsemfile
act.sa_flags = 0;        // 设置信号处理标志为0
sigemptyset(&act.sa_mask);   // 清空信号集sigaction(SIGINT,&act,NULL); // 注册信号处理函数,当接收到SIGINT信号时调用delsemfile函数key = ftok(".",100);      // 生成键值,用于创建共享内存标识符
if(key<0)
{perror("ftok");      // 如果生成失败,打印错误信息return 0;            
}shmid = shmget(key,500,0666|IPC_CREAT);  // 创建共享内存段,大小为500字节,权限为0666,如果不存在则创建
if(shmid<0)
{perror("shmget");    // 如果创建失败,打印错误信息return 0;           
}shmaddr = shmat(shmid,NULL,0);  // 将共享内存段附加到进程的地址空间,并获取共享内存地址指针sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);  // 创建名为"mysem_r"的信号量,初始值为0
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);  // 创建名为"mysem_w"的信号量,初始值为1while(1)
{sem_wait(sem_w);    // 等待名为"mysem_w"的信号量变为非零值printf(">");        // 输出提示符">"fgets(shmaddr,500,stdin);  // 从标准输入读取一行字符串,存储到共享内存中sem_post(sem_r);    // 增加名为"mysem_r"的信号量的值,表示数据已写入共享内存
}
}

tes_semr.c 读程序

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>void delsemfile(int sig)
{
sem_unlink("mysem_r");  // 删除名为"mysem_r"的信号量
exit(0);                // 退出程序
}int main(){sem_t *sem_r,*sem_w;     // 定义两个信号量指针,分别用于读和写操作
key_t key;               // 定义键值,用于生成共享内存标识符
int shmid;               // 定义共享内存标识符
char *shmaddr;           // 定义共享内存地址指针
struct sigaction act;    // 定义信号处理结构体
act.sa_handler = delsemfile;  // 设置信号处理函数为delsemfile
act.sa_flags = 0;        // 设置信号处理标志为0
sigemptyset(&act.sa_mask);   // 清空信号集sigaction(SIGINT,&act,NULL); // 注册信号处理函数,当接收到SIGINT信号时调用delsemfile函数key = ftok(".",100);      // 生成键值,用于创建共享内存标识符
if(key<0)
{perror("ftok");      // 如果生成失败,打印错误信息return 0;            
}shmid = shmget(key,500,0666|IPC_CREAT);  // 创建共享内存段,大小为500字节,权限为0666,如果不存在则创建
if(shmid<0)
{perror("shmget");    // 如果创建失败,打印错误信息return 0;            
}shmaddr = shmat(shmid,NULL,0);  // 将共享内存段附加到进程的地址空间,并获取共享内存地址指针sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);  // 创建名为"mysem_r"的信号量,初始值为0
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);  // 创建名为"mysem_w"的信号量,初始值为1while(1)
{sem_wait(sem_r);    // 等待名为"mysem_r"的信号量变为非零值printf("%s\n",shmaddr);  // 输出共享内存中的字符串sem_post(sem_w);    // 增加名为"mysem_w"的信号量的值,表示数据已读取完成
}
}

在这里插入图片描述

运行效果如图,实现了两个进程通讯

可以通过命令ls /dev/shm查看信号量文件;在Linux系统中,/dev/shm目录是用于存放共享内存和信号量文件的特殊目录

ctrl + c退出程序后,捕获到SIGINT信号后执行delsemfile()删除信号量文件

无名信号灯

用到的函数有:

  1. sem_init 初始化无名信号量
  2. sem_destory 销毁无名信号量
  3. sem_wait 信号灯P操作,申请资源
  4. sem_post 信号灯V操作,释放资源

sem_init 函数

  1. 原型

    #include <semaphore.h>
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
  2. 功能:初始化一个无名信号量

  3. 参数

    • sem:指向要初始化的信号量的指针。
    • pshared:指定信号量的类型,如果为0,则表示该信号量是进程私有的;如果为非0值,则表示该信号量是进程间共享的。
    • value:指定信号量的初始值。
  4. 返回值

    • 成功时,返回0;
    • 失败时,返回-1,并设置errno

sem_destory 函数

  1. 原型

    #include <semaphore.h>
    int sem_destroy(sem_t *sem);
    
  2. 功能:销毁一个无名信号量

  3. 参数

    • sem:指向要销毁的信号量的指针。
  4. 返回值

    • 成功时,返回0;
    • 失败时,返回-1,并设置errno
  5. 注意:只有当信号量的引用计数变为0时,才能安全地销毁它。如果还有其他线程或进程正在等待该信号量,那么销毁操作将失败,并且errno将被设置为EBUSY。在这种情况下,需要确保所有使用该信号量的线程或进程都已经结束,然后再尝试销毁它。

示例-无名信号灯使用

使用信号量和共享内存实现的简单进程间通信(IPC)示例。主要功能是在一个进程中输入字符串,另一个进程中读取并打印这些字符串。

#include <fcntl.h>
#include <sys/stat.h> 
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>sem_t sem_r,sem_w; // 定义两个信号量,用于读写同步
char *shmaddr; // 共享内存地址指针void destroysem(int sig) // 信号处理函数,用于销毁信号量并退出程序
{
sem_destroy(&sem_r); // 销毁读信号量
sem_destroy(&sem_w); // 销毁写信号量
exit(0); // 退出程序
}void *readmem(void *arg) // 读取共享内存中的字符串并打印的线程函数
{
while(1)
{sem_wait(&sem_r); // 等待读信号量printf("%s\n",shmaddr); // 打印共享内存中的字符串sem_post(&sem_w); // 释放写信号量
}
}int main()
{
key_t key; // 键值
int shmid; // 共享内存标识符struct sigaction act; // 信号处理结构体
act.sa_handler = destroysem; // 设置信号处理函数
act.sa_flags = 0; // 设置信号处理标志
sigemptyset(&act.sa_mask); // 清空信号集sigaction(SIGINT,&act,NULL); // 注册信号处理函数key = ftok(".",100); // 生成键值
if(key<0)
{perror("ftok"); // 输出错误信息return 0;
}shmid = shmget(key,500,0666|IPC_CREAT); // 创建共享内存
if(shmid<0)
{perror("shmget"); // 输出错误信息return 0;
}shmaddr = shmat(shmid,NULL,0); // 将共享内存映射到进程地址空间sem_init(&sem_r,0,0); // 初始化读信号量,初始值为0
sem_init(&sem_w,0,1); // 初始化写信号量,初始值为1pthread_t tid; // 线程标识符
pthread_create(&tid,NULL,readmem,NULL); // 创建线程,执行readmem函数while(1)
{sem_wait(&sem_w); // 等待写信号量printf(">"); // 提示用户输入fgets(shmaddr,500,stdin); // 从标准输入读取字符串并存储到共享内存中sem_post(&sem_r); // 释放读信号量
}
}

在这里插入图片描述

运行效果如图

在父进程中,它循环接收用户输入的数据并将其写入共享内存中。在子进程中,它循环读取共享内存中的数据并打印出来。

System V 信号灯

System V 信号灯是一种用于进程间同步和互斥的机制,它允许多个进程共享资源而不会产生冲突。

  1. 信号灯集合:System V 信号灯可以是一个或多个计数信号灯的集合。这意味着可以同时操作集合中的任意多个信号灯,以协调进程间的访问顺序。
  2. 信号灯操作:System V 信号灯的操作通常涉及三个步骤:初始化(创建)、P 操作(等待资源)和 V 操作(释放资源)。P 操作用于请求资源,如果资源不可用则进程将等待;V 操作用于释放资源,使得其他等待该资源的进程可以继续执行
  3. 避免死锁:通过申请多个资源时使用 System V 信号灯,可以减少死锁的风险。死锁是指两个或多个进程在互相等待对方释放资源时无法继续执行的状态。

System V 信号灯是一种重要的同步机制,它在多进程编程中扮演着关键角色,确保了资源的有序访问和数据的一致性。

用到的函数有:

  1. semget 创建/打开信号灯
  2. semop 对信号灯集合中的信号量进行P - V操作
  3. semctl 信号灯集合的控制(初始化/删除)

semget 函数

  1. 原型

    #include <sys/sem.h>
    int semget(key_t key, int nsems, int semflg);
    
  2. 功能:获取一个信号量集的标识符

  3. 参数

    • key:是一个键值,用于唯一标识一个信号量集。通常使用ftok()函数生成。
    • nsems:指定需要创建或获取的信号量数量。
    • semflg:设置信号量集的访问权限和创建标志。可以是以下值的组合:
      • IPC_CREAT:如果信号量集不存在,则创建一个新的信号量集。
      • IPC_EXCL:与IPC_CREAT一起使用,表示如果信号量集已经存在,则返回错误。
      • 0:表示不设置任何特殊标志。
  4. 返回值

    • 如果成功,返回信号量集的标识符(非负整数)。
    • 如果失败,返回-1,并设置errno为相应的错误码。

semop 函数

  1. 原型

    #include <sys/sem.h>
    int semop(int semid, struct sembuf *sops, unsigned nsops);
    
  2. 功能:改变信号量的值,对信号灯集合中的信号量进行P - V操作

  3. 参数

    • semid:是一个信号量集的标识符,由semget()函数返回。
    • sops:是一个指向struct sembuf结构体数组的指针,该数组定义了要执行的操作。
    • nsops:指定sops数组中操作的数量。

    struct sembuf结构体:

    struct sembuf {unsigned short sem_num;  // 信号量编号short sem_op;            // 操作类型short sem_flg;           // 操作标志
    };
    

    各个字段的含义如下:

    • sem_num:指定要操作的信号量的编号。如果设置为0,则表示对整个信号量集进行操作。
    • sem_op:指定要对信号量执行的操作类型。
      • sem_op> 0,它表示进程释放控制的资源,即信号量的值将增加sem_op的数量。
      • sem_op= 0,如果没有设置IPC_NOWAIT标志,调用进程将进入睡眠状态直到信号量的值为0;如果设置了该标志且信号量值不为0,则进程不会进入睡眠,而是直接返回EAGAIN。
      • sem_op< 0,它表示尝试获取资源使用权,信号量的值将增加sem_op的绝对值。如果此时信号量的值小于或等于sem_op的绝对值,操作将会阻塞,直到信号量的值大于或等于sem_op的绝对值。
    • sem_flg:指定操作的标志。可以是以下值的组合:
      • IPC_NOWAIT:非阻塞模式,如果无法立即执行操作,则立即返回。
      • SEM_UNDO:撤销之前的操作。
      • 0:不设置任何特殊标志。
  4. 返回值

    • 如果成功,返回0。
    • 如果失败,返回-1,并设置errno为相应的错误码。

semctl 函数

  1. 原型

    #include <sys/sem.h>
    int semctl(int semid, int semnum, int cmd, ...);
    
  2. 功能:控制信号量集

  3. 参数

    • semid:是一个信号量集的标识符,由semget()函数返回。
    • semnum:指定要操作的信号量的编号。如果设置为0,则表示对整个信号量集进行操作。
    • cmd:指定要执行的命令。可以是以下值之一:
      • IPC_RMID:删除信号量集。(常用)
      • IPC_SET:设置信号量集的属性。(常用)
      • IPC_STAT:获取信号量集的状态信息。(常用)
      • SETVAL:设置信号灯的值,需要用到第四个参数:共用体。(常用)
      • IPC_INFO:获取系统支持的信号量集的最大数量和当前使用的数量。
      • SEM_STAT:获取指定信号量的状态信息。
      • GETALL:获取所有信号量的值。
      • GETNCNT:获取等待某个信号量变为非零值的进程数。
      • GETPID:获取最后一个操作指定信号量的进程ID。
      • GETVAL:获取指定信号量的值。
      • GETZCNT:获取等待某个信号量变为零值的进程数。
      • SETALL:设置所有信号量的值。
    • ...:根据cmd的不同,可能需要传递额外的参数。
  4. 返回值

    • 如果成功,返回相应的结果或状态信息。
    • 如果失败,返回-1,并设置errno为相应的错误码

示例-System V信号灯使用

基于信号量和共享内存的进程间通信(IPC)示例。它创建了一个父子进程,并使用信号量和共享内存进行数据传递。

#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>#define SEM_READ   0
#define SEM_WRITE  1//semctl函数用到的参数
union semun {
int val;
};               // P操作函数,用于对信号量进行减一操作
void Poperation(int semid,int semindex)
{
struct sembuf sbuf;
sbuf.sem_num =  semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;semop(semid,&sbuf,1);
}// V操作函数,用于对信号量进行加一操作
void Voperation(int semid,int semindex)
{
struct sembuf sbuf;
sbuf.sem_num =  semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;semop(semid,&sbuf,1);
}int main()
{
key_t key; // 定义键值key
char *shmaddr; // 共享内存地址指针
int semid,shmid; // 信号量和共享内存的ID
key = ftok(".",100); // 生成键值
if(key<0)
{perror("ftok"); // 如果生成失败,输出错误信息return 0;
}semid = semget(key,2,IPC_CREAT |0666); // 创建信号量集合
if(semid<0)
{perror("semget"); // 如果创建失败,输出错误信息return 0;
}
shmid = shmget(key,500,IPC_CREAT |0666); // 创建共享内存
shmaddr = shmat(shmid,NULL,0); // 将共享内存映射到进程地址空间
union semun mysem; // 定义信号量联合体
mysem.val = 0; // 初始化读信号量的值为0
semctl(semid,SEM_READ,SETVAL,mysem); // 设置读信号量的值
mysem.val = 1; // 初始化写信号量的值为1
semctl(semid,SEM_WRITE,SETVAL,mysem); // 设置写信号量的值pid_t pid; // 定义进程ID
pid = fork(); // 创建子进程
if(pid<0)
{perror("fork"); // 如果创建失败,输出错误信息shmctl(shmid,IPC_RMID,NULL); // 删除共享内存semctl(semid,0,IPC_RMID); // 删除信号量集合exit(-1);
}else if(pid == 0)
{// 子进程循环读取共享内存中的数据并打印while(1){Poperation(semid,SEM_READ); // 对读信号量进行P操作printf("%s\n",shmaddr); // 打印共享内存中的数据Voperation(semid,SEM_WRITE); // 对写信号量进行V操作}
}
else
{// 父进程循环接收用户输入的数据并写入共享内存while(1){Poperation(semid,SEM_WRITE); // 对写信号量进行P操作printf(">"); // 提示用户输入数据fgets(shmaddr,32,stdin); // 从标准输入读取数据并写入共享内存Voperation(semid,SEM_READ); // 对读信号量进行V操作}
}
}

在这里插入图片描述

运行结果如图

在父进程中,它循环接收用户输入的数据并将其写入共享内存中。在子进程中,它循环读取共享内存中的数据并打印出来。

相关文章:

【并发程序设计】15.信号灯(信号量)

15.信号灯(信号量) Linux中的信号灯即信号量是一种用于进程间同步或互斥的机制&#xff0c;它主要用于控制对共享资源的访问。 在Linux系统中&#xff0c;信号灯作为一种进程间通信&#xff08;IPC&#xff09;的方式&#xff0c;与其他如管道、FIFO或共享内存等IPC方式不同&…...

【操作与配置】VS2017与MFC环境配置

【操作与配置】VS2017与MFC环境配置 概述 Visual Studio 是一款强大且多功能的集成开发环境&#xff08;IDE&#xff09;&#xff0c;适用于软件开发人员和团队。使用此应用程序&#xff0c;您可以构建和调试现代Web应用程序&#xff0c;并利用扩展帮助探索几乎任何编程语言。…...

遥感影像信息提取

刘老师&#xff08;副教授&#xff09;&#xff0c;来自双一流重点高校&#xff0c;长期从事GIS/RS/3S技术及其生态环境领域中的应用等方面的研究和教学工作&#xff0c;并参与GIS的二次开发&#xff0c;发表多篇sci论文&#xff0c;具有资深的技术底蕴和专业背景。 专题一&am…...

LRU算法

文章目录 LRU算法LRU 如何实现LinkedHashMap来实现的LRU算法的缓存HashMap实现LRU算法的缓存 LRU算法 LRU&#xff08;Least Recently Used&#xff09;算法可以使用哈希表和双向链表来实现。哈希表用于快速查找数据&#xff0c;双向链表用于记录数据的访问顺序。以下是LRU算法…...

JVM运行时数据区 - 程序计数器

运行时数据区 Java虚拟机在执行Java程序的过程中&#xff0c;会把它管理的内存划分成若干个不同的区域&#xff0c;这些区域有各自的用途、创建及销毁时间&#xff0c;有些区域随着虚拟机的启动一直存在&#xff0c;有些区域则随着用户线程的启动和结束而建立和销毁&#xff0…...

1.JAVA小项目(零钱通)

一、说明 博客内容&#xff1a;B站韩顺平老师的视频&#xff0c;以及代码的整理。此项目分为两个版本&#xff1a; 面向过程思路实现面向对象思路实现 韩老师视频地址&#xff1a;【【零基础 快速学Java】韩顺平 零基础30天学会Java】 https://www.bilibili.com/video/BV1fh4…...

Redis这一篇就够了

一.概述 Redis是什么&#xff1f; Redis是远程服务字典服务&#xff0c;是一个开源的使用ANSI C语言编写&#xff0c;支持网络&#xff0c;可基于内存亦可持久化的日志型&#xff0c;Key-Value数据库&#xff0c;并提供多种语言的API。 redis会周期性把更新的数据写入磁盘或把…...

Java web应用性能分析之【jvisualvm远程连接云服务器】

Java web应用性能分析之【java进程问题分析概叙】-CSDN博客 Java web应用性能分析之【java进程问题分析工具】-CSDN博客 前面整理了java进程问题分析和分析工具&#xff0c;现在可以详细看看jvisualvm的使用&#xff0c;一般java进程都是部署云服务器&#xff0c;或者托管IDC机…...

springboot发送短信验证码,结合redis 实现限制,验证码有效期2分钟,有效期内禁止再次发送,一天内发送超3次限制

springboot结合redis发送短信验证码,实现限制发送操作 前言(可忽略)实现思路正题效果图示例手机号不符合规则校验图成功发送验证码示例图redis中缓存随机数字验证码&#xff0c;2分钟后失效删除redis缓存图验证码有效期内 返回禁止重复发送图验证码24小时内发送达到3次&#xf…...

【Python】使用 Pandas 统计每行数据中的空值

缘分让我们相遇乱世以外 命运却要我们危难中相爱 也许未来遥远在光年之外 我愿守候未知里为你等待 我没想到为了你我能疯狂到 山崩海啸没有你根本不想逃 我的大脑为了你已经疯狂到 脉搏心跳没有你根本不重要 &#x1f3b5; 邓紫棋《光年之外》 在数据分析…...

1pannel部署onenav导航容器编排模板

onenav导航 1pannel部署onenav导航容器编排模板 networks:1panel-network:external: true services:onenav:container_name: onenavimage: helloz/onenav:latestlabels:createdBy: Appsnetworks:- 1panel-networkports:- 127.0.0.1:{port}:80environment:- TZAsia/Shanghaivol…...

linux--实时性优化

linux--实时性优化 1 介绍2 实时性需求3 代表性实时系统4 嵌入式系统嵌入式软件系统结构处理器时钟节拍多任务机制任务调度方式任务调度算法时间片调度算法优先级调度算法基于优先级的时间片调度算法 5 cyclictest 测试工具命令说明命令分析参数含义 6 linux 实时性改进某版本上…...

React-基础样式控制

组件基础样式方案 React组件基础的样式控制有两种方式 1、行内样式&#xff08;不推荐&#xff09; 属性名是多个单词的需要使用驼峰写法 也可以把样式都提取到一个变量里&#xff0c;再赋值到style里 2、class类名控制 classnames优化类名控制 classnames是一个简单的JS库&…...

制作ChatPDF之前端Vue搭建(二)

前端界面 接上篇: 制作ChatPDF之Elasticsearch8.13.4搭建&#xff08;一&#xff09; 为了实现一个基于 Vue.js 的前端应用&#xff0c;用户可以上传 PDF 文件&#xff0c;输入查询&#xff0c;并在输出框中显示查询结果&#xff0c;你需要以下步骤&#xff1a; 初始化 Vue …...

汽车IVI中控开发入门及进阶(二十一):DAB和FM 收音机

前言: 在过去的十年里,数字收音机对车载娱乐产生了重大影响。现在,几乎每辆新车都标配了这项技术,这也是我们60%以上的人收听收音机的方式。甚至有传言称,在不久的将来,将永久关闭调频发射机,使许多车载收音机过时。但一些相对年轻的汽车在工厂里仍然没有安装DAB,而且…...

智能sql LLM

DB-GPT&#xff1a;彻底改变数据库与私有LLM技术的交互 智能SQL生成&#xff1a;后端技术与LLM的完美结合 智能SQL生成&#xff1a;后端技术与LLM的完美结合_llm sql-CSDN博客 GitHub - eosphoros-ai/DB-GPT: AI Native Data App Development framework with AWEL(Agentic Wor…...

大聪明教你学Java | 深入浅出聊 Stream.parallel()

前言 &#x1f34a;作者简介&#xff1a; 不肯过江东丶&#xff0c;一个来自二线城市的程序员&#xff0c;致力于用“猥琐”办法解决繁琐问题&#xff0c;让复杂的问题变得通俗易懂。 &#x1f34a;支持作者&#xff1a; 点赞&#x1f44d;、关注&#x1f496;、留言&#x1f4…...

图解大模型分布式并行各种通信原语

背景 在分布式集群上执行大模型任务时候&#xff0c;往往使用到数据并行&#xff0c;流水线并行&#xff0c;张量并行等技术&#xff0c;这些技术本质上也就是对数据进行各种方案的切分&#xff0c;然后放到不同的节点上运算。不同节点在计算的过程中需要对数据分发或者同步等…...

张大哥笔记:下一个风口是什么?

我们经常会问&#xff0c;下一个风口是什么&#xff1f;我们可以大胆预测一下&#xff0c;2024年的风口是什么呢&#xff1f; 40年前&#xff0c;如果你会开车&#xff0c;那就是响当当的铁饭碗&#xff1b; 30年前&#xff0c;如果你会英语和电脑&#xff0c;那也绝对是个人才…...

AI去衣技术中的几何着色:揭秘数字时尚的魔法

在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变我们的生活&#xff0c;从智能家居到自动驾驶汽车&#xff0c;再到个性化医疗。然而&#xff0c;AI的影响远不止于此。它正在重塑我们对艺术、设计和时尚的理解。特别是在数字时尚领域&#…...

Leecode---技巧---只出现一次的数字 / 多数元素

题解&#xff1a; 利用异或运算 a⊕a 0 的性质&#xff0c;可用来消除所有出现了两次的元素&#xff0c;最后剩余的即为所得。 class Solution { public:int singleNumber(vector<int>& nums){// 初始化为0int ans 0;for(int x: nums){// 异或操作ans ^ x;}retur…...

为图片设置经纬度信息

一、java实现 小编看了很多技术博客&#xff0c;但是测试要么下载的jar包中的api和博客对不上&#xff0c;要么就是不对&#xff0c;总之没实现 Java 读取图片信息 java 写入 exif 信息 使用Java读取和修改图片的Exif信息 java获取图片的GPS信息 https://drewnoakes.com/code/e…...

密码和密钥的联系与区别

密码和密钥是两个非常重要的概念&#xff0c;但容易混淆这两者&#xff0c;以下内容介绍了它们的联系和区别&#xff1a; 一、定义 密码&#xff08;Password&#xff09;&#xff0c;在日常语境中&#xff0c;通常指的是个人为了验证自己的身份而设置的一段秘密的字符序列&am…...

C++编程法则365天一天一条(323)main函数执行之前和之后的动作

在C和C程序中&#xff0c;main 函数之前和之后执行的函数是由编译器、链接器和运行时环境共同决定的。以下是一些通常会在这些阶段执行的关键函数&#xff1a; 在 main 函数之前执行的函数 启动代码&#xff08;Start-up Code&#xff09;: 这是由编译器提供的一段代码&#…...

阿里云短信服务使用(Java)

文章目录 一、流程1.打开短信服务2.提交材料申请资质3.资质通过后&#xff0c;申请短信签名并设置短信模板4.右上角设置AccessKey5.充值 二、参考官方文档调用API1.引入maven依赖2.调用API补充 一、流程 1.打开短信服务 登陆注册阿里云 搜索“短信服务”&#xff0c;点击“免…...

C++17之std::void_t

目录 1.std::void_t 的原理 2.std::void_t 的应用 2.1.判断成员存在性 2.1.1.判断嵌套类型定义 2.1.2 判断成员是否存在 2.2 判断表达式是否合法 2.2.1 判断是否支持前置运算符 2.2.3 判断两个类型是否可做加法运算 3.std::void_t 与 std::enable_if 1.std::void_t 的…...

零基础入门篇①⑥ Python可变序列类型--字典

Python从入门到精通系列专栏面向零基础以及需要进阶的读者倾心打造,9.9元订阅即可享受付费专栏权益,一个专栏带你吃透Python,专栏分为零基础入门篇、模块篇、网络爬虫篇、Web开发篇、办公自动化篇、数据分析篇…学习不断,持续更新,火热订阅中🔥专栏限时一个月(5.8~6.8)重…...

C语言面试题1-10

C语言中的内存管理及相关问题探讨 在C语言编程中&#xff0c;内存管理是一个至关重要的概念&#xff0c;掌握内存的分布及其操作不仅能够提高代码效率&#xff0c;还能避免常见的内存泄漏等问题。本文将详细介绍C语言中内存的分布、堆区和栈区的区别、标识符的命名规则、定义和…...

Qt Designer工具如何修改MainWindow窗口的标题

Qt Designer工具如何修改MainWindow窗口的标题 在MainWindow的属性编辑器中选择“windowTitle”后面一栏修改成期望的窗口标题名称即可。 按住“ctrlR”即可查看可视化界面的窗口标题...

车辆前向碰撞预警系统性能要求和测试规程

前言 本文整理《GB/T 33577-2017 智能运输系统-车辆前向碰撞预警系统性能要求和测试规程》国标文件关键信息,FCW系统性能和测试右给深层次的认识。 术语和定义 车辆前向碰撞预警系统 forward vehicle collision warning system自车 subject vehicle(SV)目标车辆 target ve…...