『 Linux 』信号的捕捉及部分子问题

文章目录

    • 信号的捕捉
    • sigaction函数
    • 未决信号集的置零时机
    • 信号处理过程的阻塞
    • 可重入函数
    • volatile 关键字
    • SIGCHLD 信号


信号的捕捉

请添加图片描述

该图为基于信号处理为用户自定义动作的图解;

  • 信号的捕捉

    当一个信号被递达时,如果该信号的处理动作是用户自定义的函数(如int sighandler(int))时就会调用这个函数,该步骤被称为捕捉信号;

  • 用户程序注册新号处理函数

    用户程序中注册了SIGQUIT信号的处理函数sighandler;

  • 切换到内核态

    当前正在执行main函数时发生了中断或是异常,这导致程序从用户态切换到内核态(如图中标注的12);

  • 内核态处理信号

    内核在处理完中断或异常之后,在返回用户态的main函数前检测到有信号SIGQUIT递达(图中标注2);

  • 独立控制流程

    sighandlermain函数使用不同的堆栈空间,之间没有调用和被调用的关系,是两个独立的控制流程(图中标注4);

  • 返回用户态

    sighandler函数返回后通过执行的特殊的系统调用sys_sigreturn再次进入内核(图中标注4);

  • 恢复主流程

    如果没有新的信号被注册,这次返回用户态时将恢复main函数中断前的上下文继续执行(图中标注5);


sigaction函数

请添加图片描述

sigaction()函数与signal()函数相同,功能都为捕捉信号;

NAMEsigaction - examine and change a signal actionSYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);Feature Test Macro Requirements for glibc (see feature_test_macros(7)):sigaction(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCEsiginfo_t: _POSIX_C_SOURCE >= 199309LRETURN VALUEsigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.

该函数的具体功能用于检查和改变指定型号的处理方式;

它允许程序定义或更改信号处理函数,也可以获取当前信号处理函数的信息;

函数调用成功时返回0,调用失败时返回-1并设置errno;

参数如下:

  • int signum

    表示传入一个int类型的参数,该参数表明需要传入的信号编号;

  • const struct sigaction *act

    传入一个struct sigaction *参数,其中加了const作修饰表示是一个传入型参数;

    该参数用于指定新的信号处理动作,其结构体包含以下成员:

    struct sigaction {void (*sa_handler)(int);      // 信号处理函数指针void (*sa_sigaction)(int, siginfo_t *, void *); // 另一个信号处理函数指针,用于接收额外信息sigset_t sa_mask;             // 在信号处理期间需要阻塞的信号集int sa_flags;                 // 影响信号处理行为的标志位void (*sa_restorer)(void);    // 不常用的字段,一般设置为NULL
    };
    

    在处理普通信号时只需要关注void (*sa_handler)(int)成员与sigset_t sa_masksigset_t sa_mask成员即可;

    • void (*sa_handler)(int)

      该成员为一个函数指针类型,用于指向信号处理函数,如SIG_IGN,SIG_DFL或是用户自定义函数void userhandler(int);

    • sigset_t sa_mask

      该成员为一个sigset_t类型的数据,其中sigset_t类型为操作系统封装的一个位图结构用于依靠该结构对信号集进行操作;

      当一个进程在处理一个信号的时候会将对应的信号添加到其信号屏蔽字(阻塞信号集)中以避免信号方法重复调用;

      设置该成员可以使得进程在处理一个信号时同时将多个信号添加至信号屏蔽字中;

    其余成员可默认设为0;

  • struct sigaction *oldact

    该参数类型与act类型相同,不被const修饰为一个输出型参数;

    该参数用于保存之前的信号处理动作,如果不需要保存旧的信号处理则传递nullptr;

void sighandler(int signo) {// 自定义处理动作printf("sighandler get a signal: %d\n", signo);
}int main() {struct sigaction act, oldact;memset(&act, 0, sizeof(act));  // 利用memset()初始化结构体memset(&act, 0, sizeof(oldact));act.sa_handler = sighandler;  // 为成员赋值 设置自定义处理动作int n = sigaction(SIGINT, &act, &oldact);  // 函数调用while (!n) {                               // 循环打印cout << "I am a Process ,id : " << getpid() << endl;sleep(1);}return 0;
}

该例子未使用可以同时阻塞多个信号的特性;

运行结果为:

$ ./mysignal 
I am a Process ,id : 29791
^Csighandler get a signal: 2
I am a Process ,id : 29791
^Csighandler get a signal: 2
I am a Process ,id : 29791
^\Quit

当使用Ctrl + C对进程发送SIGINT信号时被捕捉而后执行自定义动作;


未决信号集的置零时机

请添加图片描述

当信号被处理后未决信号集对应位置应置零从而表示该信号已经被处理完毕为递达状态;

实际上未决信号集的置零时机为执行信号处理前,即先将未决信号集对应位置置零再执行处理信号的函数;

以上文代码为基础进行修改:

void PrintPending() {sigset_t pending;sigemptyset(&pending);sigpending(&pending);for (int i = 31; i > 0; --i) {if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}void sighandler(int signo) {// 自定义处理动作PrintPending();printf("sighandler get a signal: %d\n", signo);
}int main() {struct sigaction act, oldact;memset(&act, 0, sizeof(act));  // 利用memset()初始化结构体memset(&act, 0, sizeof(oldact));act.sa_handler = sighandler;  // 为成员赋值 设置自定义处理动作int n = sigaction(SIGINT, &act, &oldact);  // 函数调用while (!n) {                               // 循环打印cout << "I am a Process ,id : " << getpid() << endl;sleep(1);}return 0;
}

添加了PrintPending()函数用于打印整张Pending位图;

运行结果为:

$ ./mysignal 
I am a Process ,id : 29858
^C0000000000000000000000000000000
sighandler get a signal: 2
I am a Process ,id : 29858
^C0000000000000000000000000000000
sighandler get a signal: 2
I am a Process ,id : 29858
^C0000000000000000000000000000000

当进行信号处理并对Pending位图进行打印时并未出现SIGINT位置变为1的情况;

这意味着实际上在进行信号处理函数前对应的Pending位图已经被置零;


信号处理过程的阻塞

请添加图片描述

当一个信号在被进行处理时将会把正在处理的信号添加进信号屏蔽字以阻塞下一个相同的信号;

本质上是防止同一个信号处理函数被重复调用;

void PrintPending() {sigset_t pending;sigemptyset(&pending);sigpending(&pending);for (int i = 31; i > 0; --i) {if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}void sighandler(int signo) {// 自定义处理动作//   PrintPending();printf("sighandler get a signal: %d\n", signo);while (1) {PrintPending();cout << endl;sleep(1);}
}int main() {struct sigaction act, oldact;memset(&act, 0, sizeof(act));  // 利用memset()初始化结构体memset(&act, 0, sizeof(oldact));act.sa_handler = sighandler;  // 为成员赋值 设置自定义处理动作int n = sigaction(SIGINT, &act, &oldact);  // 函数调用while (!n) {                               // 循环打印cout << "I am a Process ,id : " << getpid() << endl;sleep(1);}return 0;
}

该程序中当进程捕获到一个信号时将会调用用户自定义动作进行信号处理;

用户自定义动作为打印对应捕捉到的信号而后无限循环打印pending表以验证相同信号在第二次注册时是否会被阻塞保留其未决状态;

运行并在另一个窗口中使用kill -signo <pid>命令向进程发送SIGINT信号,结果为:

$ ./mysignal 
I am a Process ,id : 29921
sighandler get a signal: 20000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000100000000000000000000000000000010^\Quit

当第一次注册SIGINT信号时将执行用户自定义动作,即先打印sighandler get a signal: 2再循环打印pending表;

一开始的pendingSIGINT信号处已经置零表示正在对该信号进行处理;

当再次注册SIGINT信号时由于上一个相同信号未被处理完成,处于未递达状态,相同的信号被阻塞,停留在未决信号集中;

可调用sigaction()函数并设置struct sigaction *act成员中的sigset_t sa_mask位图以能够在进行一个信号的处理时阻塞多个信号;

void PrintPending() {sigset_t pending;sigemptyset(&pending);sigpending(&pending);for (int i = 31; i > 0; --i) {if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}void sighandler(int signo) {// 自定义处理动作//   PrintPending();printf("sighandler get a signal: %d\n", signo);while (1) {PrintPending();cout << endl;sleep(1);}
}int main() {struct sigaction act, oldact;memset(&act, 0, sizeof(act));  // 利用memset()初始化结构体memset(&act, 0, sizeof(oldact));sigset_t mask;sigemptyset(&mask);// 使用sigaddset()设置信号集sigaddset(&mask, 3);sigaddset(&mask, 4);sigaddset(&mask, 1);sigaddset(&mask, 5);act.sa_mask = mask;  // 将设置好的信号集进行赋值从而设置处理时阻塞act.sa_handler = sighandler;  // 为成员赋值 设置自定义处理动作int n = sigaction(SIGINT, &act, &oldact);  // 函数调用while (!n) {                               // 循环打印cout << "I am a Process ,id : " << getpid() << endl;sleep(1);}return 0;
}

该函数中定义了一个sigset_t类型的mask,并用sigaddset()将信号1,3,4,5分别加入了信号集,并将该sigset_t类型赋值给sa_mask成员,使其阻塞多个信号;

运行程序并在另一个终端中使用kill命令分别以2,1,2,3,4,5的顺序依次注册信号并观察结果;

对应的结果为:

$ ./mysignal 
I am a Process ,id : 30083
I am a Process ,id : 30083
sighandler get a signal: 2
00000000000000000000000000000100000000000000000000000000000011000000000000000000000000000011100000000000000000000000000011110000000000000000000000000011111

结果为首先处理2号信号SIGINT并进入死循环打印pending表;

向进程再次发送1-5号信号其都被阻塞至未决信号集中;


可重入函数

请添加图片描述

当一个进程在调用一个函数,在函数执行过程中接收到了一个信号发生了中断;

而信号的处理中又需要调用一次该函数,若是出现了数据错误或是数据不一致等错误问题则称该函数为不可重入函数,反之则称为可重入函数;

以该图为例;

全局环境中存在一个链表并对其进行一次头插操作,其头插操作的核心代码为:

	/* 伪代码 */
insert(...){// ...NodeA->next = head;head = &NodeA;// ...
}

即将新头插的节点的next指针指向head所指向的节点;

head重新指向新插入的头结点;

若是在插入过程中,即执行完了NodeA -> next = head;后进程接收到了一个信号并对信号进行处理;

而在信号的处理中需要调用insert()函数进行头插而再次执行该函数时这个过程被称为 “重入” ,即相同函数重复进入;

当执行主控制流时执行insert()函数,在调用insert()时接收信号并处理型号,在处理信号中再次调用了一次insert();

流程图如下:

结果为main()函数和sighandler()先后向链表中插入两个头结点;

而最后只有一个节点被真正插入链表中,使得另一个节点出现 节点丢失 的内存泄漏问题;

这意味着该insert()函数为一个不可重入函数;

可重入函数 , 不可重入函数 都只为一个函数的特点;

如果一个函数符合以下条件之一的则是不可重入函数:

  • 调用了mallocfree

    malloc也是用全局链表来管理堆的;

  • 调用了标准I/O库函数

    标准I/O库的很多实现都以不可重入的方式使用全局数据堆;


volatile 关键字

请添加图片描述

编译器在编译时可以将代码进行优化;

对应的使用不同优化级别使编译时对代码进行优化,常见的有-O0,-O1,-O2,-O3等选项,其中-O0为默认优化,即表示不进行优化;

volatile关键字是用来修饰一个变量以防止编译器过度优化;

int flag = 1;void sighandler(int signo) {printf("sighandler get a signal: %d\n", signo);flag = 0;printf("falg : %d\n", flag);
}int main() {signal(SIGINT, sighandler);while (flag);cout << "process quit sucess" << endl;
}

在这段代码中定义了一个全局变量,且设置了对SIGINT信号的捕获;

在主控制流main函数中使用whileflag为条件进行循环,当条件为真时循环,条件为假时跳出循环;

当捕捉到SIGINT信号时对该信号进行处理,处理方案为使用自定义动作,为将全局变量flag设为0,即条件为假并对当前flag进行一次打印;

使用g++-O3选项进行编译并进行编译优化;

g++ -o mysignal mysignal.cc -g -O3 -Wall -std=c++11

运行该程序并向该程序发送2号信号SIGINT;

$ ./mysignal 
^Csighandler get a signal: 2
falg : 0
^Csighandler get a signal: 2
falg : 0
^Csighandler get a signal: 2
falg : 0

从结果看出,即使注册了SIGINT信号且执行了自定义动作将全局变量flag设为了0但进程仍不退出;

本质原因是由于进行了优化后,其flag参数将被存放至寄存器当中,而main()函数中的循环条件始终以寄存器中的flag进行条件判断,修改的flag确实内存中的flag;

两者出现了隔离,寄存器不会向内存再去读取flag变量而是由于while循环不停判断寄存器中的变量从而使得进程无法正常退出;

该行为即为编译器的一种编译过度优化;

可通过使用volatile关键字修饰来放置过度优化行为;

int flag修改为volatile int flag;

重新使用-O3选项编译并运行,并使用Ctrl + C向进程发送一个2号信号SIGINT信号;

$ ./mysignal 
^Csighandler get a signal: 2
falg : 0
process quit sucess
$ 

结果为使用Ctrl + C发送2号信号SIGINT时进程被终止;


SIGCHLD 信号

请添加图片描述

当一个子进程退出时将会为其父进程发送一个信号,该信号为17号信号SIGCHLD;

该信号默认行为SIG_DFL为忽略;

可在父进程中调用signal()接口捕捉SIGCHLD函数进行验证;

void sighandler(int signo) {sleep(2);printf("parent process catch a signal: %d\n", signo);cout << endl;waitpid(-1, nullptr, WNOHANG); // 不考虑获取进程退出信息 参数2设置为nullptr
}int main() {signal(SIGCHLD, sighandler);pid_t id = fork();if (id == 0) {// childint cnt = 2;while (cnt--) {printf("I am child process,the PID is %d\n", getpid());cout << endl;sleep(1);}cout << "child process quit...." << endl<<endl;;exit(-1);}// parentwhile (1) {printf("I am parent process,the PID is %d\n", getpid());cout << endl;sleep(1);}return 0;
}

在父进程中使用signal()设置捕获17号信号SIGCHLD信号,并设置自定义动作为调用waitpid(-1,nullptr,WNOHANG)以非阻式来等待子进程退出;

fork()创建子进程,子进程在2s后退出进程并向父进程发送17号信号SIGCHLD;

当父进程获取到子进程所发的信号时将该信号进行捕获,而后调用自定义动作对已经僵尸的子进程进行等待处理;

运行程序并在另一个窗口使用shell脚本:

$ while :; do ps axj | head -1 && ps axj | grep mysignal | grep -v grep ; echo "----------------------------" ; sleep 1 ; done

观察父子进程的状态;

运行结果为:

$ ./mysignal 
I am parent process,the PID is 32131I am child process,the PID is 32132I am parent process,the PID is 32131I am child process,the PID is 32132I am parent process,the PID is 32131child process quit....parent process catch a signal: 17I am parent process,the PID is 32131I am parent process,the PID is 32131

2s过后子进程退出,父进程捕获到17号信号并对子进程进行waitpid()清理;

另一个会话显示的结果为:

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
32131 32132 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
32131 32132 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
32131 32132 32131 29111 pts/0    32131 Z+    1001   0:00 [mysignal] <defunct>
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
32131 32132 32131 29111 pts/0    32131 Z+    1001   0:00 [mysignal] <defunct>
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32131 32131 29111 pts/0    32131 S+    1001   0:00 ./mysignal
----------------------------
^C

子进程僵尸了2s后被父进程回收;

若是不需要子进程的退出信息时父进程可对子进程发出的信号使用signal()进行忽略从而避免子进程停留在僵尸;

以上述代码为基础进行修改:

int main() {
//   signal(SIGCHLD, sighandler);signal(SIGCHLD, SIG_IGN);pid_t id = fork();if (id == 0) {// childint cnt = 2;while (cnt--) {printf("I am child process,the PID is %d\n", getpid());cout << endl;sleep(1);}cout << "child process quit...." << endl<<endl;;exit(-1);}// parentwhile (1) {printf("I am parent process,the PID is %d\n", getpid());cout << endl;sleep(1);}return 0;
}

对应结果为:

 # 程序所在会话$ ./mysignal 
I am parent process,the PID is 32213I am child process,the PID is 32214I am parent process,the PID is 32213I am child process,the PID is 32214I am parent process,the PID is 32213child process quit....I am parent process,the PID is 32213I am parent process,the PID is 32213^C# 脚本所在会话PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32213 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
32213 32214 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32213 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
32213 32214 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32213 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
----------------------------PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29111 32213 32213 29111 pts/0    32213 S+    1001   0:00 ./mysignal
----------------------------

结果表示子进程退出时直接退出,其父进程并未接收到子进程的SIGCHLD而是直接将其进行忽略处理;

shell脚本所在会话显示子进程并未在僵尸状态下进行停留而是直接退出;

  • SIGCHLD信号的忽略行为

    SIGCHLD的默认动作是忽略与对SIGCHLD信号进行忽略是两种概念;

    第一种为默认动作实际上是调用signal(SIGCHLD,SIG_DFL),而其对应的行为为忽略;

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

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

相关文章

文物实时状态监控的保护系统

文物是宝贵的历史遗产和文化瑰宝&#xff0c;其保护是我们共同的责任。为了实现文物的安全保护&#xff0c;现代科技提供了各种环境监测设备&#xff0c;可以实时监控文物的状态并采取相应的保护措施。本文将介绍一种利用各种环境监测设备实现文物实时状态监控的保护系统。 一…

【视频讲解】ResNet深度学习神经网络原理及其在图像分类中的应用|附Python代码

全文链接&#xff1a;https://tecdat.cn/?p37134 原文出处&#xff1a;拓端数据部落公众号 分析师&#xff1a;Canglin Li 本文深入探讨了卷积层&#xff08;Convolutional Layer&#xff09;在深度学习框架中的核心作用与操作机制&#xff0c;并分析了其在特征提取、网络构…

近期代码报错解决笔记

1.TypeError: ‘bool’ object is not callable 想print("Type of head:", type(entity_emb[head]))&#xff0c;结果报如下错误&#xff1a; 源代码&#xff1a; 因为 print 仍然被当作一个布尔值处理&#xff0c;而不是作为函数调用。这个问题的根源在于 print …

Adobe Photoshop(Ps)安装包软件下载

一、Adobe Photoshop简介 Adobe Photoshop&#xff08;简称PS&#xff09;是由Adobe Systems公司开发的图像处理软件&#xff0c;它是一款集图像扫描、编辑修改、图像制作、广告创意、图像输入与输出于一体的图形图像处理软件。广泛应用于专业测评、平面设计、广告摄影、影像创…

学习小型gpt源码(自用)

数据集构建_哔哩哔哩_bilibili &#xff08;b站上有一系列课&#xff0c;从数据处理到模型构建和训练使用&#xff09; 什么是batch&#xff1f; 为什么一个batch内的句子要一样长&#xff1f; 不同batch的长度可以不一样&#xff0c;但是同一个batch内长度一样&#xff01;…

【MySQL进阶之路 | 高级篇】数据操作类型的角度理解共享锁,排他锁

1. 从数据操作的类型划分&#xff1a;读锁&#xff0c;写锁 对于数据库并发事务的读-读情况并不会引起什么问题。对于写-写&#xff0c;读-写操作或写-写操作这些情况可能会引起一些问题&#xff0c;需要使用MVCC或者加锁的方式来解决它们。在使用加锁的方式解决问题时&#x…

3.3-LSTM的改进

文章目录 1改进点1.1多层化1.2 dropout1.2.1具体概念1.2.2应该插入到LSTM模型的哪里 1.3权重共享 2改进之后的LSTMLM的代码实现2.1初始化2.2前向计算2.3反向传播 3相应的学习代码的实现4总结 1改进点 1.1多层化 加深神经网络的层数往往能够学习更复杂的模式&#xff1b;因此这…

利用换元法计算积分的常见题型(考研高数复习)

考研中常见的几种换元法积分计算题 (1)被积式仅包含一个根式&#xff1a;根号下为有 a a a 和 x x x 的平方和/平方差 此种类型的积分题型&#xff0c;可以通过构造单个锐角大小为 t t t 的直角三角形&#xff0c;利用勾股定理和三角函数进行代换。 平方和的情况 形如 ∫…

java学习----注释

简介 override介绍&#xff1a; 添加了这个注释其实是做了个语法校验的作用 override定义 Deprecated介绍&#xff1a; 源码&#xff1a; SuppressWarnings介绍&#xff1a; 源码&#xff1a; 元注解 Retention注解介绍&#xff1a; 案列 Target注解介绍&#xff1a; Documente…

LLM - 理解 Transformer 的位置编码 sin cos 的作用与原理

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/140697827 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 Transformer 模型中的位置编码是关键技术,通过为每个词嵌入向量添加位…

数据库第五次作业

1. 触发器 建立触发器&#xff0c;订单表中增加订单数量后&#xff0c;商品表商品数量同步减少对应的商品订单出数量,并测试 建立触发器&#xff0c;实现功能:客户取消订单&#xff0c;恢复商品表对应商品的数量 建立触发器&#xff0c;实现功能:客户修改订单&#xff0c;商品…

【微软蓝屏】微软Windows蓝屏问题汇总与应对解决策略

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

2024年铜川宜君半程马拉松,暴晒+爬坡152安全完赛

1、赛事背景 2024年7月21日&#xff0c;我参加了2024年铜川宜君半程马拉松赛&#xff0c;7月举办的赛事很少&#xff0c;全国都算温度比较高的&#xff0c;虽然宜君是一个山城&#xff0c;还是会担心气温会高。 临开赛1、2周&#xff0c;陕西区域降水比较多&#xff0c;赛前一…

【算法专题】双指针算法之LCR 179. 查找总价格为目标值的两个商品(力扣)

欢迎来到 CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a;双指针算法之LCR 179. 查找总价格为目标值的两个商品&#xff08;力扣&#xff09; &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法…

使用nginx解决本地环境访问线上接口跨域问题

前言 前端项目开发过程中&#xff0c;经常会遇到各种各样的跨域问题。 虽然大部分时候&#xff0c;由脚手架自带的proxy功能即可解决问题&#xff0c;如webpack&#xff0c;vite等&#xff1b;但是若没有通过脚手架搭建项目&#xff0c;或者必须使用某些特殊规则转发时&#…

了解光耦合器从基础到应用

光耦合器也称为光电耦合器&#xff0c;是一种利用光信号传递电信号的电子元器件。它广泛应用于各种电子设备和电路中&#xff0c;因其在隔离电气信号、提高抗干扰能力方面的独特优势&#xff0c;备受工程师们的青睐。本文将为光耦爱好者和高级工程师提供一份有关光耦合器的知识…

解锁人工智能学习中的数学密钥

一、启航&#xff1a;奠定数学基础 1. 线性代数&#xff1a;AI的入门语言 学习目标&#xff1a;掌握向量、矩阵的基本概念及运算&#xff0c;理解线性空间、线性变换及特征值、特征向量的意义。学习建议&#xff1a;从基础教材入手&#xff0c;如《线性代数及其应用》&#x…

企业级视频拍摄与编辑SDK的全面解决方案

视频已成为企业传播信息、展示品牌、连接用户的重要桥梁&#xff0c;如何高效、专业地制作高质量视频内容&#xff0c;成为众多企业面临的共同挑战。美摄科技&#xff0c;作为视音频技术领域的创新先锋&#xff0c;以其强大的视频拍摄与编辑SDK&#xff0c;为企业量身打造了一站…

Sip for Mac:强大的屏幕取色软件

Sip for Mac是一款功能强大的屏幕取色工具软件&#xff0c;专为设计师、开发者和创作者打造。这款软件以其精准的取色功能和丰富的颜色管理选项而备受好评。 Sip的核心功能是提供多种取色工具&#xff0c;包括拾色器、取色板和屏幕取色等&#xff0c;使用户能够轻松地从屏幕上…

分享几种电商平台商品数据的批量自动抓取方式

在当今数字化时代&#xff0c;电商平台作为商品交易的重要渠道&#xff0c;其数据对于商家、市场分析师及数据科学家来说具有极高的价值。批量自动抓取电商平台商品数据成为提升业务效率、优化市场策略的重要手段。本文将详细介绍几种主流的电商平台商品数据批量自动抓取方式&a…