【Linux系统编程】信号

目录

    • 1、信号
      • 1.1、什么是信号
      • 1.2、进程对信号的处理
      • 1.3、信号的生命周期
      • 1.4、信号处理流程
      • 1.5、信号的发送
    • 2、kill()、raise()函数 发送信号
    • 3、alarm函数 闹钟信号
    • 4、pause函数 挂起信号、暂停
    • 5、singal 函数 捕获信号
      • 5.1、为什么返回值是上一次的处理方式
      • 5.2、练习
    • 6、sigaction 函数 捕获信号(推荐使用)
    • 7、setitimer() 闹钟信号
    • 8、sigset_t相关函数 信号集
    • 9、sigprocmask 函数 设置信号屏蔽
    • 10、sigpending 函数

1、信号

1.1、什么是信号

信号(signal):信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也称作软中断。信号用来通知进程发生了异步事件。

信号事件的发生有两个来源:
硬件来源,比如我们按下了键盘或者其它硬件;
软件来源,最常用发送信号的系统函数是kill(), raise(), alarm()和setitimer()等函数,软件来源还包括一些非法运算等操作。
注意:信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
kill -l:
在这里插入图片描述

1.2、进程对信号的处理

在这里插入图片描述
进程可以通过三种方式来响应和处理一个信号:

  • 1、忽略信号:忽略某个信号,对该信号不做任何处理,就像未发生过一样。
  • 2、捕捉信号:是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理
  • 3、执行缺省操作:对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止

常见的信号:
在这里插入图片描述
处理动作的字母含义如下
A 缺省的动作是终止进程
B 缺省的动作是忽略此信号
C 缺省的动作是终止进程并进行内核映像转储(dump core)
D 缺省的动作是停止进程
E 信号不能被捕获
F 信号不能被忽略

停止进程:只是暂停了进程的执行,进程仍然存在于内存中,随时可以恢复其执行1。
终止进程:进程被彻底结束,其资源被释放,不再占用系统内存1
在终端中停止进程
按下 Ctrl+Z 可以停止当前前台进程。

$ sleep 100
^Z
[1]+  Stopped                 sleep 100

使用 jobs 命令查看停止的进程:

$ jobs
[1]+  Stopped                 sleep 100

使用 fg 命令恢复进程:

$ fg 1
sleep 100

在这里插入图片描述

1.3、信号的生命周期

从信号(Signal)发送到信号处理函数的执行是一个完整的信号生命周期
在这里插入图片描述

1.4、信号处理流程

在这里插入图片描述

1.5、信号的发送

除了内核和超级用户,并不是每个进程都可以向其他的进程发送信号。

一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号。

常用的发送信号的函数有kill()raise ()alarm()setitimer()abort() 等。

2、kill()、raise()函数 发送信号

kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill函数的一个用户接口)。
kill –l 命令查看系统支持的信号列表
命令:man 2 kill
kill可以向指定的进程发送信号
在这里插入图片描述

在这里插入图片描述
命令:man 2 raise
raise只能向本进程发送信号
在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int i;for (i = 0; i < 3; i++){printf("i = %d\n",i);sleep(1);if (i == 1){if (kill(getpid(),SIGINT) < 0)         //向指定进程发送信号{perror("kill signal error");}// if (raise(SIGINT) < 0)          //只能向本进程发送信号// {//     perror("raise error");// }}}}
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("parent work start\n");sleep(1);printf("parent work over\n");
}int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){sleep(1); //让父进程先执行printf("child process start\n");kill(getppid(),SIGINT);printf("chiild process over\n");}else if(pid > 0){printf("parent process start\n");signal(SIGINT,sigfun);wait(NULL);printf("parent process over\n");}
}

3、alarm函数 闹钟信号

在这里插入图片描述
alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALRM信号。
在这里插入图片描述

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main()
{int res = alarm(5);printf("res = %d\n",res);sleep(1);res = alarm(7);printf("res = %d\n",res);  //返回上一个定时器剩余时间int i = 0;while(1){printf("i = %d\n",i++);sleep(1);}
}

4、pause函数 挂起信号、暂停

pause() 进程挂起直到收到信号为止,并以缺省的方式处理此信号
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){printf("child process send signal\n");sleep(3);kill(getppid(),SIGINT);   //向父进程发送信号}else if(pid > 0){printf("parent process start\n");pause();//进程挂起 (暂停),等待被信号触发,会以系统默认方式处理信号printf("parent over\n");}
}

5、singal 函数 捕获信号

在这里插入图片描述
在这里插入图片描述
出错时返回SIG_ERR

函数说明:当指定的信号signum到达时就会跳转到参数handler函数中执行。handler函数的参数接收的是信号值(比如SIGINT是2)

附加说明 在信号发生跳转到handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,如果要改变此操作请改用sigaction()。
比如这个例子

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 信号处理函数
void handle_signal(int signum) {printf("接收到信号: %d\n", signum);
}int main() {// 注册信号处理函数if (signal(SIGINT, handle_signal) == SIG_ERR) {perror("signal 注册失败");return 1;}printf("程序运行中,按下 Ctrl+C 发送 SIGINT 信号...\n");// 无限循环,等待信号while (1) {sleep(1);}return 0;
}

第二次 按下 ctrl + c 会以系统默认方式中止,
也有例外
如果你的程序在第二次按下 Ctrl+C 时没有中止,是因为 signal() 的行为在 glibc 实现中不会恢复为默认行为。
如果你希望明确控制信号处理行为,建议使用 sigaction() 替代 signal()
在这里插入图片描述

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int signum)
{if (signum == SIGINT){printf("is SIGINT\n");}elseprintf("is other signal\n");}
int main()
{// signal(SIGINT,SIG_IGN);  //以忽略方式处理signal(SIGINT,SIG_DFL);  //以缺省方式处理#if 1void (*fun_res)(int) = signal(SIGINT,sigfun);   //自定义信号处理方式printf("fun_res = %p\n",fun_res);  //第一次返回值为NULLfun_res = signal(SIGINT,sigfun);printf("fun_res = %p sigfun = %p\n",fun_res,sigfun);  //第二次返回上一次的处理方式#endifint i = 0;while(i < 5){printf("i = %d\n",i++);sleep(1);}
}

5.1、为什么返回值是上一次的处理方式

#include <stdio.h>
#include <signal.h>
#include <unistd.h>//为什么返回值是上一次signal处理方式
// 链式处理(Chaining)是一种常见的技术,
// 它允许程序员在自定义信号处理函数中调用之前的处理函数。
// 这种技术通常用于在自定义处理逻辑中保留系统默认行为或之前设置的行为。// 保存之前的信号处理函数
void (*old_handler)(int);// 自定义信号处理函数
void custom_handler(int signum)
{printf("Custom handler: Received signal %d\n", signum);// 调用之前的信号处理函数// if (old_handler != SIG_ERR) {//     printf("Calling old handler...\n");//     old_handler(signum);// }// 调用之前的信号处理函数(如果存在且不是默认行为)if (old_handler != SIG_ERR && old_handler != SIG_DFL) {printf("Calling old handler...\n");old_handler(signum);} else {printf("Old handler is SIG_DFL or invalid, skipping...\n");}
}int main()
{// 保存之前的信号处理函数old_handler = signal(SIGINT, custom_handler);if (old_handler == SIG_ERR) {perror("signal");return 1;}printf("Press Ctrl+C to trigger the custom handler...\n");// 无限循环,等待信号while (1) {sleep(1);}return 0;
}

5.2、练习

创建一个子进程,父进程每隔2秒向子进程发送一个不同的信号(SIGINT,SIGQUIT,SIGALRM), 子进程注册相应的信号处理
函数,处理接收到信号,在信号处理函数里面打印信号名称,子进程接收到3个信号后结束

// 创建一个子进程,父进程每隔2秒向子进程发送一个不同的信号(SIGINT,SIGQUIT,SIGALRM),
// 子进程注册相应的信号处理
// 函数,处理接收到信号,在信号处理函数里面打印信号名称,子进程接收到3个信号后结束#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("is SIGINT\n");
}void sigfun2(int sig)
{printf("is SIGQUIT\n");
}void sigfun3(int sig)
{printf("is SIGALRM\n");
}int main()
{signal(SIGINT,sigfun);signal(SIGQUIT,sigfun2);signal(SIGALRM,sigfun3);pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0 ){int num = 0;while (1){num++;pause();if(num == 3)exit(0);}}else if(pid > 0){int sigs[] = {SIGINT,SIGQUIT,SIGALRM};for (int i = 0; i < 3; i++){sleep(2);kill(pid,sigs[i]);}wait(NULL);}}

6、sigaction 函数 捕获信号(推荐使用)

头文件

 #include <signal.h>

函数定义

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数:

  • signum:要捕获的信号编号(例如 SIGINT、SIGTERM 等)。
  • act:指向 struct sigaction 结构体的指针,用于指定新的信号处理行为。
  • oldact:指向 struct sigaction 结构体的指针,用于保存之前的信号处理行为(可以为 NULL)。

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

struct sigaction 结构体

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
};
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT(signfun)\n");
}void sigfun2(int sig)
{printf("is SIGINT(signfun2)\n");
}int main()
{struct sigaction newact = {0};newact.sa_handler = sigfun;     // 设置信号处理函数// sigemptyset(&newact.sa_mask);      // 清空信号屏蔽集// newact.sa_flags = 0;               // 默认标志sigaction(SIGINT,&newact,NULL);struct sigaction oldact = {0};newact.sa_handler = sigfun2;sigaction(SIGINT,&newact,&oldact);printf("oldact = %p signal = %p\n",oldact.sa_handler,sigfun);int i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}printf("-----------------------\n");//恢复信号处理方式newact.sa_handler = SIG_DFL;sigaction(SIGINT,&newact,NULL);i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}}

7、setitimer() 闹钟信号

alarm()只会产生一次闹钟信号
setitimer()循环产生闹钟信号

头文件

#include <sys/time.h>

函数定义

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

参数说明

  • which: 指定定时器类型,可以是 ITIMER_REALITIMER_VIRTUALITIMER_PROF
  • new_value: 指向 struct itimerval 结构的指针,用于设置新的定时器值。
  • old_value: 指向 struct itimerval 结构的指针,用于存储旧的定时器值。如果不需要旧的定时器值,可以传入
    NULL。

ITIMER_REAL: 真实时间定时器,触发 SIGALRM 信号。

ITIMER_VIRTUAL: 进程的用户态 CPU 时间定时器,触发 SIGVTALRM 信号。

ITIMER_PROF: 进程的用户态和内核态 CPU 时间定时器,触发 SIGPROF 信号。
struct itimerval 结构

struct itimerval {struct timeval it_interval; /* 定时器间隔 */struct timeval it_value;   /* 当前定时器值 */
};struct timeval {time_t      tv_sec;        /* 秒 */suseconds_t tv_usec;       /* 微秒 */
};

返回值

  • 成功时返回 0,失败时返回 -1 并设置 errno。
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("SIGALRM\n");
}int main()
{//用于捕捉信号signal(SIGALRM,sigfun);struct itimerval newtime = {0};newtime.it_interval.tv_sec = 1; //间隔时间newtime.it_value.tv_sec = 3;  //第一次发送信号时间setitimer(ITIMER_REAL,&newtime,NULL);   int i;for ( i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}}

8、sigset_t相关函数 信号集

在这里插入图片描述

typedef struct {unsigned long __bits[__SIGSET_NWORDS];
} sigset_t;

头文件

#include <signal.h>

函数定义

//初始化一个空的信号集,即清除所有信号。
int sigemptyset(sigset_t *set);
//初始化一个包含所有信号的信号集
int sigfillset(sigset_t *set);
//将指定信号添加到信号集中
int sigaddset(sigset_t *set, int signum);
//从信号集中删除指定信号。
int sigdelset(sigset_t *set, int signum);
//检查指定信号是否在信号集中。
int sigismember(const sigset_t *set, int signum);

sigemptyset函数
功能:初始化一个空的信号集,即清除所有信号。

参数:
set:指向信号集的指针。

返回值:

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

sigfillset函数
功能:初始化一个包含所有信号的信号集。

参数:
set:指向信号集的指针。

返回值:

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

sigaddset函数
功能:将指定信号添加到信号集中。

参数:
set:指向信号集的指针。
signum:要添加的信号编号(如 SIGINT)。

返回值:

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

sigdelset函数
功能:从信号集中删除指定信号。

参数:
set:指向信号集的指针。
signum:要删除的信号编号(如 SIGINT)。

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

sigismember函数
功能:检查指定信号是否在信号集中。

参数:
set:指向信号集的指针。
signum:要检查的信号编号(如 SIGINT)。

返回值:
如果信号在信号集中,返回 1。
如果信号不在信号集中,返回 0。
失败时返回 -1,并设置 errno。

#include <stdio.h>
#include <signal.h>//打印一个字节里每一位的值
void printb(char byte)
{for (int i = 0; i < 8; i++){printf("%d",byte & 1 << 7 - i ? 1 : 0);}printf(" "); //8位空一格}//打印某一块区域所有字节的数据
//dest区域的首地址     size区域的大小
void printmem(void *dest,size_t size)
{//从高字节向低字节输出int i = 0;for ( i = 0; i < size; i++){printb(((char *)dest)[size -1 -i]);//每八个字节换一行if ((i + 1)%8 == 0)printf("\n");         }}int main()
{sigset_t set = {0};   //声明一个信号集结构体//填充整个信号集if(sigfillset(&set) < 0){perror("sigfillset error");return -1;}//清空信号集if (sigemptyset(&set) < 0){perror("sigemptyset error");return -1;}//添加信号if(sigaddset(&set,SIGINT) < 0){perror("sigaddset error");return -1;}if(sigaddset(&set,SIGQUIT) < 0){perror("sigaddset error");return -1;}// printmem(&set,sizeof(set));//删除信号if(sigdelset(&set,SIGQUIT) < 0){perror("sigdelset error");return -1;}printmem(&set,sizeof(set));//查询信号是否存在int res = sigismember(&set,SIGQUIT);if (res == 1){printf("set have SIGINT\n");}else if (res == 0)printf("set not have SIGINT\n");else if (res < 0){perror("sigismember error");return -1;}}

9、sigprocmask 函数 设置信号屏蔽

信号屏蔽 不等于 信号忽略
信号忽略:忽略并丢掉
信号屏蔽:暂时不处理,放入队列中,后面再从队列中取出处理
在这里插入图片描述

头文件

#include <signal.h>

函数定义

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明

  • how:指定如何修改信号屏蔽字,可以是以下值之一

    • SIG_BLOCK:将 set 中的信号添加到当前信号屏蔽字中(阻塞这些信号)。
    • SIG_UNBLOCK:从当前信号屏蔽字中移除 set 中的信号(解除阻塞这些信号)。
    • SIG_SETMASK:将当前信号屏蔽字替换为 set 中的信号集。
  • set:指向sigset_t类型的信号集,表示要修改的信号集。如果为 NULL,则忽略此参数。

  • oldset:指向sigset_t 类型的信号集,用于保存修改前的信号屏蔽字。如果为 NULL,则不保存。

注意
SIG_BLOCK:用于临时阻塞某些信号,不影响其他已阻塞的信号。

SIG_SETMASK:用于完全替换信号屏蔽字,适合需要精确控制信号屏蔽字的场景。

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}
int main()
{//注册对SIGINT信号的捕捉signal(SIGINT,sigfun);//初始化信号集sigset_t set = {0};sigemptyset(&set);  //先清空sigaddset(&set,SIGINT); //再添加//设置信号屏蔽//这里SIG_if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//解除屏蔽,信号会被触发if(sigprocmask(SIG_UNBLOCK,&set,NULL) < 0){perror("sigprocmask error");return -1;}}

10、sigpending 函数

头文件

#include <signal.h>

函数定义

int sigpending(sigset_t *set);

功能
用于检查当前进程中被阻塞(屏蔽)但尚未处理的信号的函数。

参数

set:一个指向 sigset_t 类型的指针,用于存储当前被阻塞且未处理的信号集。

返回值

  • 成功:返回 0。
  • 失败:返回 -1,并设置 errno 以指示错误。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}int main()
{//注册对SIGINT信号的捕捉signal(SIGINT,sigfun);//初始化信号集sigset_t set = {0};sigemptyset(&set);  //先清空sigaddset(&set,SIGINT); //再添加//设置信号屏蔽if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//获取屏蔽信号的信号集sigset_t PendSet = {0};if (sigpending(&PendSet) < 0){perror("sigpending error");return -1;}int res = sigismember(&PendSet,SIGINT);if(res == 1)printf("have SIGINT\n");else if(res == 0)printf("not have SIGINT\n");else if(res < 0){perror("sigismember error");return -1;}}

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

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

相关文章

git使用命令总结

文章目录 Git 复制创建提交步骤Git 全局设置:创建 git 仓库:已有仓库? 遇到问题解决办法&#xff1a;问题一先git pull一下&#xff0c;具体流程为以下几步&#xff1a; 详细步骤 Git 复制 git clone -b RobotModelSetting/develop https://gitlab.123/PROJECT/123.git创建提…

解锁 AI 核心:神经网络与机器学习知名算法全解析

引言​ 在人工智能蓬勃发展的当下&#xff0c;神经网络与机器学习算法作为核心驱动力&#xff0c;广泛应用于各个领域。了解这些知名算法&#xff0c;能让我们更好地把握 AI 技术的精髓。接下来&#xff0c;一同深入探寻。​ 机器学习知名算法​ 线性回归&#xff08;Linear…

基于SpringBoot + Vue 的房屋租赁系统

基于springboot的房屋租赁管理系统-带万字文档 SpringBootVue房屋租赁管理系统 送文档 本项目有前台和后台两部分、多角色模块、不同角色权限不一样 共分三种角色&#xff1a;用户、管理员、房东 管理员&#xff1a;个人中心、房屋类型管理、房屋信息管理、预约看房管理、合…

30天学习Java第六天——Object类

Object类 java.lang.Object时所有类的超类。Java中所有类都实现了这个类中的方法。 toString方法 将Java对象转换成字符串的表示形式。 public String toString() {return getClass().getName() "" Integer.toHexString(hashCode()); }默认实现是&#xff1a;完…

DeepSeek在金融行业应用

引言 随着人工智能技术的快速发展&#xff0c;DeepSeek作为一款国产大模型&#xff0c;凭借其强大的语义理解、逻辑推理和多模态处理能力&#xff0c;在金融行业迅速崭露头角。其低成本、高效率和开源特性使其成为金融机构智能化转型的重要工具。本文旨在分析DeepSeek在金融行业…

【Unity】 HTFramework框架(六十二)Agent编辑器通用智能体(AI Agent)

更新日期&#xff1a;2025年3月14日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 编辑器通用智能体AIAgent类Friday&#xff08;星期五&#xff09;启用智能体设置智能体类型开放智能体权限智能体交互资源优化批处理运行代码联网搜索休闲…

以太坊AI代理与PoS升级点燃3月市场热情,2025年能否再创新高?

币热网深度报道&#xff1a;以太坊AI代理与PoS升级引爆3月热潮&#xff0c;2025年能否再攀历史新高&#xff1f; 原文来源&#xff1a;币热网 - 区块链信息资讯平台 以太坊升级&#xff0c;市场热情高涨 近期&#xff0c;以太坊市场犹如被一股神秘力量点燃&#xff0c;掀起了…

【赵渝强老师】达梦数据库的目录结构

达梦数据库安装成功后&#xff0c;通过使用Linux的tree命令可以非常方便地查看DM 8的目录结构。 tree -L 1 -d /home/dmdba/dmdbms#输出的信息如下&#xff1a; /home/dmdba/dmdbms ├── bin 存放DM数据库的可执行文件&#xff0c;例如disql命令等。 ├── bin2 ├── d…

2025探索短剧行业新可能报告40+份汇总解读|附PDF下载

原文链接&#xff1a;https://tecdat.cn/?p41043 近年来&#xff0c;短剧以其紧凑的剧情、碎片化的观看体验&#xff0c;迅速吸引了大量用户。百度作为互联网巨头&#xff0c;在短剧领域积极布局。从早期建立行业专属模型冷启动&#xff0c;到如今构建完整的商业生态&#xf…

基于java(springboot+mybatis)汽车信息管理系统设计和实现以及文档

基于java(springbootmybatis)汽车信息管理系统设计和实现以及文档 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各…

线程同步:多线程编程的核心机制

一、线程同步的意义 线程同步的主要目的是避免数据竞争、保证数据一致性、控制线程执行顺序&#xff0c;并提高程序的性能和稳定性。具体意义包括&#xff1a; ​避免数据竞争&#xff1a;防止多个线程同时修改共享资源&#xff0c;导致不可预测的行为。​保证数据一致性&…

Qt QML实现弹球消砖块小游戏

前言 弹球消砖块游戏想必大家都玩过&#xff0c;很简单的小游戏&#xff0c;通过移动挡板反弹下落的小球&#xff0c;然后撞击砖块将其消除。本文使用QML来简单实现这个小游戏。 效果图&#xff1a; 正文 代码目录结构如下&#xff1a; 首先是小球部分&#xff0c;逻辑比较麻…

Android自动化测试工具

细解自动化测试工具 Airtest-CSDN博客 以下是几种常见的Android应用自动化测试工具&#xff1a; Appium&#xff1a;支持多种编程语言&#xff0c;如Java、Python、Ruby、JavaScript等。可以用于Web应用程序和原生应用程序的自动化测试&#xff0c;并支持iOS和Android平台。E…

消息队列实现 Exactly Once,看 Pulsar 是怎样实现的。

大家好 &#xff0c;我是君哥。 在使用消息队列时&#xff0c;我们希望消息能够精准推送&#xff08;Exactly Once&#xff09;&#xff0c;不会丢失、也不会重复。Exactly Once 其实是很难实现的&#xff0c;Pulsar 这款消息中间件使用事务消息实现了 Exactly Once&#xff0…

Audacity的安装和使用

安装 下载地址&#xff1a;官方网站&#xff1a;Audacity 软件开源免费&#xff0c;但部分功能可能需要额外插件。 一.介绍 Audacity 是一款免费、开源的音频编辑软件&#xff0c;适用于Windows、macOS、Linux等操作系统。它支持多轨编辑、录音、音频效果处理、格式转换等功…

C++:类和对象(从底层编译开始)详解[前篇]

目录 一.inline内联的详细介绍 &#xff08;1&#xff09;为什么在调用内联函数时不需要建立栈帧&#xff1a; &#xff08;2&#xff09;为什么inline声明和定义分离到两个文件会产生链接错误&#xff0c;链接是什么&#xff0c;为什么没有函数地址&#xff1a; 二.类&…

【蓝桥】-动态规划-倒水

目录 一、问题描述​ 二、解题思路 三、完整代码 二维dp 使用滚动数组 一、问题描述 二、解题思路 一个变种的01背包问题&#xff1a; 不选该物品&#xff1a;获得固定收益 e 选择方案1&#xff1a;消耗体积 a&#xff0c;获得价值 b 选择方案2&#xff1a;消耗体积 c&…

【软考网工-实践篇】DHCP 动态主机配置协议

一、DHCP简介 DHCP&#xff0c;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议。 位置&#xff1a;DHCP常见运行于路由器上&#xff0c;作为DHCP服务器功能&#xff1a;用于自动分配IP地址及其他网络参数给网络中的设备作用&#xff1a;简化网络管理&…

使用 Arduino 和 ThingSpeak 通过互联网进行实时温度和湿度监测

使用 ThingSpeak 和 Arduino 通过 Internet 进行温度和湿度监控 湿度和温度是许多地方(如农场、温室、医疗、工业家庭和办公室)非常常见的测量参数。我们已经介绍了使用 Arduino 进行湿度和温度测量,并在 LCD 上显示数据。 在这个物联网项目中,我们将使用ThingSpeak在互联…

电子电子架构 --- 车载ECU信息安全

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…