【C++高并发服务器WebServer】-6:信号

news2025/1/28 11:44:00

在这里插入图片描述

本文目录

  • 信号的概念
    • 1.1 core文件
    • 1.2 kill命令
    • 1.3 alarm函数
    • 1.4 setitimer调用
    • 1.5 signal捕捉信号
    • 1.6 信号集
    • 1.7 内核实现信号捕捉的过程
    • 1.8 sigaction
    • 1.9 sigchld

信号的概念

信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

  • 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C 通常会给进程 发送一个中断信号。
  • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的内存区域。
  • 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。
  • 运行 kill 命令或调用 kill 函数。

使用信号主要是两个目的,一个是让进程知道已经发生了一个特定的事情,强迫进程执行它自己代码中的信号处理程序。

信号的特点有:简单、不能携带大量信息、满足某个特定条件才能发送、优先级比较高。

查看系统定义的信号列表:kill -l,前31个信号为常规信号,其余为实时信号。

在这里插入图片描述
在这里插入图片描述
下面是所有进程对应的事件表。
在这里插入图片描述
信号的5种默认处理动作有:Term终止进程、Ign当前进程忽略这个信号、Core终止进程 并且生成一个Core文件(保存进程异常退出的错误信息)、Stop暂停当前进程、Cont继续执行当前被暂停的进程。

信号的三种状态:产生、未决、递达。其中比较特殊的是SIGKILL和SIGSTOP,信号不能 被捕捉、阻塞或者忽略,只能执行默认操作。

1.1 core文件

我们 创建一个简单的代码,就是如下的代码。然后试着编译运行,会发现会报错:段错误(核心已转储)

#include <stdio.h>
#include <string.h>

int main() {
    char * buf;//
    strcpy(buf, "hello");
    return 0;
}

通过命令ulimit -a可以查看到对应的文件大小,但是core file size这是0,所以我们可以进行对应的修改。
在这里插入图片描述
通过命令ulimit -c 1024我们可以进行修改,修改之后可以再进行对应的查看。

在这里插入图片描述
通过编译上面的代码为core.c然后运行,会发现生成的core文件很大,如下所示:
在这里插入图片描述
通过命令gdb a.out进入到调试界面,然后输入命令core-file core可以查看对应的core文件信息。

看到core中写着:程序通过SIGSEGV(访问了非法内存)信号终止了,后面也有对应的出错代码位置。

在这里插入图片描述

1.2 kill命令

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
    - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig
    - 参数:
        - pid :
            > 0 : 将信号发送给指定的进程
            = 0 : 将信号发送给当前的进程组
            = -1 : 将信号发送给每一个有权限接收这个信号的进程
            < -1 : 这个pid=某个进程组的ID取反 (-12345)
        - sig : 需要发送的信号的编号或者是宏值(不同的系统,
        编号可能会不一样),一般使用宏值,0表示不发送任何信号

    kill(getppid(), 9);
    kill(getpid(), 9);
    
int raise(int sig);
    - 功能:给当前进程发送信号
    - 参数:
        - sig : 要发送的信号
    - 返回值:
        - 成功 0
        - 失败 非0
    kill(getpid(), sig); 这个命令等同于raise(9);

void abort(void);
    - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程
    kill(getpid(), SIGABRT);

通过下面的demo可以模拟对应的kill命令的过程。

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

int main() {

    pid_t pid = fork();

    if(pid == 0) {
        // 子进程
        int i = 0;
        for(i = 0; i < 5; i++) {
            printf("child process\n");
            sleep(1);
        }

    } else if(pid > 0) {
        // 父进程
        printf("parent process\n");
        sleep(2);
        printf("kill child process now\n");
        kill(pid, SIGINT);
    }

    return 0;
}

1.3 alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
    - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
            函数会给当前的进程发送一个信号:SIGALARM
    - 参数:
        seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
                取消一个定时器,通过alarm(0)。
    - 返回值:
        - 之前没有定时器,返回0
        - 之前有定时器,返回之前的定时器剩余的时间

- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
    alarm(10);  -> 返回0 (第一次调用)
    过了1秒
    alarm(5);   -> 返回9(返回之前的定时器剩余时间)

alarm(100) -> 该函数是不阻塞的
#include <stdio.h>
#include <unistd.h>

int main() {

    int seconds = alarm(5);
    printf("seconds = %d\n", seconds);  // 0

    sleep(2);
    seconds = alarm(2);    // 不阻塞
    printf("seconds = %d\n", seconds);  // 3

    while(1) { //设置while循环观察alarm(2)的效果
    }

    return 0;
}

定时器与进程的状态无关(就算进程中有sleep),alarm也会继续,alarm采用的时自然定时法。

进程实际运行的时间 = 内核时间+用户时间+消耗的时间(IO等)

1.4 setitimer调用

首先,struct itimerval是一个结构体,用于定义定时器的参数。它的定义如下:

struct itimerval {
    struct timeval it_interval; // 定时器的间隔时间
    struct timeval it_value;    // 定时器的初始时间
};

其中struct timeval的定义为,也就是这个itimerval是多级嵌套的结构体:

struct timeval {
    time_t tv_sec;  // 秒
    suseconds_t tv_usec; // 微秒
};
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,
                    struct itimerval *old_value);

    - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微秒us,可以实现周期性定时
    - 参数:
        - which : 定时器以什么时间计时
          ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用
          ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM
          ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF

        - new_value: 设置定时器的属性
        
            struct itimerval {      // 定时器的结构体
            struct timeval it_interval;  // 每个阶段的时间,间隔时间
            struct timeval it_value;     // 延迟多长时间执行定时器
            };

            struct timeval {        // 时间的结构体
                time_t      tv_sec;     //  秒数     
                suseconds_t tv_usec;    //  微秒    
            };
       
        - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
    
    - 返回值:
        成功 0
        失败 -1 并设置错误号

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;


    int ret = setitimer(ITIMER_REAL, &new_value, NULL); 
    // 非阻塞的,执行完这个命令后下面的printf会立即执行。
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }
    return 0;
}

1.5 signal捕捉信号

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
    - 功能:设置某个信号的捕捉行为
    - 参数:
        - signum: 要捕捉的信号
        - handler: 捕捉到信号要如何处理
            - SIG_IGN : 忽略信号
            - SIG_DFL : 使用信号默认的行为
            - 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
            回调函数:
                - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
                - 不是程序员调用,而是当信号产生,由内核调用
                - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。

    - 返回值:
        成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
        失败,返回SIG_ERR,设置错误号

比较特别的是: SIGKILL SIGSTOP不能被捕捉,不能被忽略。

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    // 注册信号捕捉
    // signal(SIGALRM, SIG_IGN);
    // signal(SIGALRM, SIG_DFL);
    // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。
    signal(SIGALRM, myalarm);

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    getchar();

    return 0;
}

1.6 信号集

许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。

在 PCB 中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。

信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

进程的内核区有PCB,PCB中有PID、PPID、文件描述符等,同时也有未决信号集、阻塞信号集等这些东西。

在这里插入图片描述

来简述一下未决信号集和阻塞信号集的过程:

第一段:信号的产生与未决状态

用户通过键盘按下 Ctrl + C,会触发系统产生编号为2的信号 SIGINT。然而,信号产生后并未立即被处理,而是处于“未决”状态。在内核中,所有未被处理的信号会被存储在一个特殊的集合中,称为“未决信号集”。对于 SIGINT 信号,其状态被记录在未决信号集的第二个标志位上。如果该标志位的值为 0,则表示信号当前不是未决状态;如果值为 1,则表示信号处于未决状态,等待被处理。

第二段:信号处理与阻塞机制

在处理未决信号之前,需要将其与另一个信号集——“阻塞信号集”进行比较。阻塞信号集默认情况下不阻塞任何信号,但用户可以通过调用系统的API来设置某些信号的阻塞状态。在处理信号时,系统会查询阻塞信号集中对应的标志位,判断该信号是否被设置为阻塞。如果未被阻塞,信号将被正常处理;如果被阻塞,则信号继续保持未决状态,直到阻塞解除后,信号才会被处理。

以下信号集相关的函数都是对自定义的信号集进行操作。

int sigemptyset(sigset_t *set);
    - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
    - 参数:set,传出参数,需要操作的信号集
    - 返回值:成功返回0, 失败返回-1

int sigfillset(sigset_t *set);
    - 功能:将信号集中的所有的标志位置为1
    - 参数:set,传出参数,需要操作的信号集
    - 返回值:成功返回0, 失败返回-1

int sigaddset(sigset_t *set, int signum);
    - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
    - 参数:
        - set:传出参数,需要操作的信号集
        - signum:需要设置阻塞的那个信号
    - 返回值:成功返回0, 失败返回-1

int sigdelset(sigset_t *set, int signum);
    - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
    - 参数:
        - set:传出参数,需要操作的信号集
        - signum:需要设置不阻塞的那个信号
    - 返回值:成功返回0, 失败返回-1

int sigismember(const sigset_t *set, int signum);
    - 功能:判断某个信号是否阻塞
    - 参数:
        - set:需要操作的信号集
        - signum:需要判断的那个信号
    - 返回值:
        1 : signum被阻塞
        0 : signum不阻塞
        -1 : 失败
#include <signal.h>
#include <stdio.h>
#include <bits/types/sigset_t.h>

int main() {

    // 创建一个信号集
    sigset_t set;

    // 清空信号集的内容
    sigemptyset(&set);

    // 判断 SIGINT 是否在信号集 set 里
    int ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }

    // 添加几个信号到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    // 判断SIGINT是否在信号集中
    ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }

    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }

    // 从信号集中删除一个信号
    sigdelset(&set, SIGQUIT);

    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }

    return 0;
}

运行上面的代码,可以看到下面的结果。

在这里插入图片描述

1.7 内核实现信号捕捉的过程

在这里插入图片描述

1.8 sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
                        struct sigaction *oldact);

    - 功能:检查或者改变信号的处理。信号捕捉
    - 参数:
        - signum : 需要捕捉的信号的编号或者宏值(信号的名称)
        - act :捕捉到信号之后的处理动作
        - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
    - 返回值:
        成功 0
        失败 -1

 struct sigaction {
    // 函数指针,指向的函数就是信号捕捉到之后的处理函数
    void     (*sa_handler)(int);
    
    // 不常用
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    
    // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
    sigset_t   sa_mask;
    
    // 使用哪一个信号处理对捕捉到的信号进行处理
    // 这个值可以是0,表示使用sa_handler; 也可以是SA_SIGINFO表示使用sa_sigaction
    int        sa_flags;
    
    // 被废弃掉了
    void     (*sa_restorer)(void);
};
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = myalarm;
    sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集
   
    // 注册信号捕捉
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    // getchar();
    while(1);

    return 0;
}

1.9 sigchld

SIGCHLD信号在三种情况下会被发送给父进程:当子进程结束、子进程暂停(例如被信号暂停执行)或子进程从暂停状态恢复运行时。然而,默认情况下,父进程会忽略SIGCHLD信号。通过正确处理SIGCHLD信号,父进程可以在子进程结束时及时调用wait或waitpid函数来收集子进程的状态信息并清理其资源,从而避免子进程变成僵尸进程。这种方法是解决僵尸进程问题的有效手段。

下面的demo是创建多个子进程,并在父进程中处理子进程结束时产生的SIGCHLD信号。为了避免在信号处理函数注册完成之前子进程就已经结束并发送SIGCHLD信号,代码首先将SIGCHLD信号阻塞,直到信号处理函数注册完成后再解除阻塞。父进程会持续运行并打印其进程ID,而子进程会在创建后立即退出。

首先初始化信号集并阻塞SIGCHLD信号:使用sigset_t类型定义一个信号集set,并通过sigemptyset将其初始化为空。
用sigaddset将SIGCHLD信号添加到信号集中。调用sigprocmask,将SIGCHLD信号添加到当前进程的信号屏蔽集中,从而阻塞SIGCHLD信号,防止其在信号处理函数注册完成之前被处理。

使用fork函数创建多个子进程。在for循环中,每次调用fork都会创建一个子进程。子进程在创建后立即退出,而父进程会继续运行。子进程退出时会向父进程发送SIGCHLD信号,但由于信号已经被阻塞,因此不会立即触发信号处理函数。

在父进程中,定义一个struct sigaction结构体act,并设置其sa_handler为自定义的信号处理函数myFun。
使用sigemptyset清空act.sa_mask,确保信号处理函数执行时不会被其他信号中断。
调用sigaction将SIGCHLD信号的处理函数设置为myFun。
调用sigprocmask,将SIGCHLD信号从信号屏蔽集中移除,解除阻塞,允许信号被处理。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
#include <bits/types/sigset_t.h>
#include <bits/sigaction.h>

void myFun(int num) {
    printf("捕捉到的信号 :%d\n", num);
    // 回收子进程PCB的资源
    // while(1) {
    //     wait(NULL); 
    // }
    while(1) {
       int ret = waitpid(-1, NULL, WNOHANG);
       if(ret > 0) {
           printf("child die , pid = %d\n", ret);
       } else if(ret == 0) {
           // 说明还有子进程或者
           break;
       } else if(ret == -1) {
           // 没有子进程
           break;
       }
    }
}

int main() {

    // 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);

    // 这是一个函数,用于设置当前进程的信号屏蔽集。
    // 信号屏蔽集决定了哪些信号会被暂时阻塞(即不会被立即处理)。
    // SIG_BLOCK:这是一个常量,表示将信号集中的信号添加到当前进程的信号屏蔽集中,即阻塞这些信号。
    sigprocmask(SIG_BLOCK, &set, NULL);

    // 创建一些子进程
    pid_t pid;
    for(int i = 0; i < 20; i++) {
        pid = fork();
        if(pid == 0) {
            break;
        }
    }

    if(pid > 0) {
        // 父进程

        // 捕捉子进程死亡时发送的SIGCHLD信号
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);
        sigaction(SIGCHLD, &act, NULL);

        // 注册完信号捕捉以后,解除阻塞  
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        while(1) {
            printf("parent process pid : %d\n", getpid());
            sleep(2);
        }
    } else if( pid == 0) {
        // 子进程
        printf("child process pid : %d\n", getpid());
    }

    return 0;
}

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

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

相关文章

【数据分享】1929-2024年全球站点的逐月平均能见度(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff01;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2024年全球气象站点…

【PyTorch】3.张量类型转换

个人主页&#xff1a;Icomi 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术&#xff0c;能够处理复杂的数据模式。通过 PyTorch&#xff0…

不解释快上车

聊一聊 最近有小伙伴问我有小红书图片和短视频下载的软件吗&#xff0c;我心想&#xff0c;下载那上面的图片和视频做什么&#xff1f;也许是自己没有这方面的需求&#xff0c;不了解。 不过话又说回来&#xff0c;有些很多下载器可能作者没有持续的维护&#xff0c;所以可能…

C++红黑树详解

文章目录 红黑树概念规则为什么最长路径不超过最短路径的二倍&#xff1f;红黑树的时间复杂度红黑树的结构插入叔叔节点情况的讨论只变色(叔叔存在且为红)抽象的情况变色单旋&#xff08;叔叔不存在或叔叔存在且为黑&#xff09;变色双旋&#xff08;叔叔不存在或叔叔存在且为黑…

csapp2.4节——浮点数

目录 二进制小数 十进制小数转二进制小数 IEEE浮点表示 规格化表示 非规格化表示 特殊值 舍入 浮点运算 二进制小数 类比十进制中的小数&#xff0c;可定义出二进制小数 例如1010.0101 小数点后的权重从-1开始递减。 十进制小数转二进制小数 整数部分使用辗转相除…

神经网络|(一)加权平均法,感知机和神经元

【1】引言 从这篇文章开始&#xff0c;将记述对神经网络知识的探索。相关文章都是学习过程中的感悟和理解&#xff0c;如有雷同或者南辕北辙的表述&#xff0c;请大家多多包涵。 【2】加权平均法 在数学课本和数理统计课本中&#xff0c;我们总会遇到求一组数据平均值的做法…

Spring 框架:配置缓存管理器、注解参数与过期时间

在 Spring 框架中&#xff0c;可通过多种方式配置缓存具体行为&#xff0c;常见配置方法如下。 1. 缓存管理器&#xff08;CacheManager&#xff09;配置 基于内存的缓存管理器配置&#xff08;以SimpleCacheManager为例&#xff09; SimpleCacheManager 是 Spring 提供的简单…

FPGA实现任意角度视频旋转(完结)视频任意角度旋转实现

本文主要介绍如何基于FPGA实现视频的任意角度旋转&#xff0c;关于视频180度实时旋转、90/270度视频无裁剪旋转&#xff0c;请见本专栏前面的文章&#xff0c;旋转效果示意图如下&#xff1a; 为了实时对比旋转效果&#xff0c;采用分屏显示进行处理&#xff0c;左边代表旋转…

openlayer getLayerById 根据id获取layer图层

背景&#xff1a; 在项目中使用getLayerById获取图层&#xff0c;这个getLayerById()方法不是openlayer官方文档自带的&#xff0c;而是自己封装的一个方法&#xff0c;这个封装的方法的思路是&#xff1a;遍历所有的layer&#xff0c;根据唯一标识【可能是id&#xff0c;也可能…

设计模式-建造者模式、原型模式

目录 建造者模式 定义 类图 优缺点 角色 建造者模式和工厂模式比较 使用案例 原型模式 定义 类图 优缺点 应用场景 应用类型 浅克隆 深克隆 建造者模式 定义 将一个复杂的对象的构造与它的表示分离&#xff0c;使同样的构建过程可以创建不同的表示&#xff0c;…

PTMD2.0-疾病相关的翻译后修饰数据库

翻译后修饰&#xff08;PTMs&#xff0c;post-translational modifications&#xff09;通过调节蛋白质功能参与了几乎所有的生物学过程&#xff0c;而 PTMs 的异常状态常常与人类疾病相关。在此&#xff0c;PTMD 2.0展示与疾病相关的 PTMs 综合数据库&#xff0c;其中包含 93 …

【Git版本控制器--3】Git的远程操作

目录 理解分布式版本控制系统 创建远程仓库 仓库被创建后的配置信息 克隆远程仓库 https克隆仓库 ssh克隆仓库 向远程仓库推送 拉取远程仓库 忽略特殊文件 为什么要忽略特殊文件&#xff1f; 如何配置忽略特殊文件&#xff1f; 配置命令别名 标签管理 理…

批量创建ES索引

7.x from elasticsearch import Elasticsearch# 配置 Elasticsearch 连接 # 替换为你的 Elasticsearch 地址、端口、用户名和密码 es Elasticsearch([http://10.10.x.x:43885],basic_auth(admin, XN272G9THEAPYD5N5QORX3PB1TSQELLB) )# # 测试连接 # try: # # 尝试获取集…

MySQL中的读锁与写锁:概念与作用深度剖析

MySQL中的读锁与写锁&#xff1a;概念与作用深度剖析 在MySQL数据库的并发控制机制中&#xff0c;读锁和写锁起着至关重要的作用。它们是确保数据在多用户环境下能够正确、安全地被访问和修改的关键工具。 一、读锁&#xff08;共享锁&#xff09;概念 读锁&#xff0c;也称为…

专利申请的价值

独占市场 一种产品只要授权专利权&#xff0c;等于在市场上拥有独占权。 政策奖励 各地方政府均出台响应文件&#xff0c; 对专利申请者进行奖励或者补助。 申报项目 申报高新技术企业、创新基金等 各类计划、项目的必要前提条件 专利申请 技术保护 防止新的技术与产品被他人 抄…

使用 OpenCV 和 Python 轻松实现人脸检测

目录 一、准备工作 二、加载人脸检测模型 三、读取图像并进行人脸检测 四、处理视频中的人脸检测 五、优化人脸检测效果 六、总结 在人工智能和计算机视觉领域,人脸检测是一项非常基础且重要的技术。通过人脸检测,我们可以在图像或视频中识别并定位人脸,进而进行后续的…

自然语言处理——从原理、经典模型到应用

1. 概述 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是一门借助计算机技术研究人类语言的科学&#xff0c;是人工智能领域的一个分支&#xff0c;旨在让计算机理解、生成和处理人类语言。其核心任务是将非结构化的自然语言转换为机器可以…

2025年新开局!谁在引领汽车AI风潮?

汽车AI革命已来。 在2025年伊始开幕的CES展上&#xff0c;AI汽车、AI座舱无疑成为了今年汽车行业的最大热点。其中不少车企在2025年CES上展示了其新一代AI座舱&#xff0c;为下一代智能汽车的人机交互、场景创新率先打样。 其中&#xff0c;东软集团也携带AI驱动、大数据支撑…

YOLO目标检测3

一. 参考资料 《YOLO目标检测》 by 杨建华博士 本篇文章的主要内容来自于这本书&#xff0c;只是作为学习记录进行分享。 二. 搭建YOLOv1的网络 2.1 YOLOv1的网络结构 作者带我们构建的YOLOv1网络是一个全卷积结构&#xff0c;其中不包含任何全连接层&#xff0c;这一点可以…

css3 svg制作404页面动画效果HTML源码

源码介绍 css3 svg制作404页面动画效果HTML源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果 效果预览 源码如下 <!doctype html> <html> <head> <meta charse…