网站兼容哪些浏览器/今天最新疫情情况
索引
- 一.初始信号
- 1.什么是信号
- 2.前后台进程
- 3.信号的种类
- 4.信号的管理
- 二.信号产生前
- 1.验证键盘是可以产生信号的
- 2.通过系统调用接口发送信号
- 3.由软件条件产生信号
- 4.硬件异常产生信号
- 5.总结
- 6.core dump
- 信号产生中
- 1.信号在内核中的表示
- 2.信号集操作函数
- 信号产生后
- 1.了解内核态和用户态
- 2.内存如何实现信号的捕捉
- 3.sigaction
一.初始信号
1.什么是信号
生活的角度: 红绿灯,闹钟,下课铃等
1.我们是如何得知这些东西的?有人教,(能够认识这些场景下的信号以及其表示的含义)也就是能够识别这些信号
2.我们提前知道这些信号产生时要做什么也就是我们已经提前知道了信号处理的方法
从上述可以看出
即使信号没有产生,我们已经具备了处理信号的能力!
因此:信号是给进程发送的,进程要具备处理信号的能力
1.该能力一定是预先已经早就有了的
2.进程能够识别对应的信号
3.进程能够处理对应的信号
这个能力是OS给我们提供的
对于进程来讲,即使信号还没有产生,我们进程已经具有识别和处理这个信号的能力了
2.前后台进程
while (true){sleep(1);}return 0;
当我们直接运行上述程序时,该程序会变成一个前台进程,此时直接ctrl+C
可以直接终止,是因为ctrl+C
可以发送一个信号给前台进程,使得该进程退出。
但当我们将前台进程变成后台进程时,其接不到类似ctrl + C
的信号,也就无法退出了
理解用户按下
Ctrl + C
,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出。
注意:
- 1.只有前台进程才能收到Ctrl+C产生的信号,后台进程无法收到,一个运行进程的命令后面+&可以使得前台进程转化成后台进程,转化为后台进程之后shell不必等到进程结束才可以接受新的命令,可以直接启动新进程
- 2.前台进程在运行过程中用户随时按下
ctrl+C
而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能受到SIGINT
(就是Ctrl+C
)而终止,所以信号相对于进程的控制流程来说是异步的。
3.信号的种类
kill -l 可以显示信号列表
数字是信号编号,右侧是宏,二者一个意思
1-31是分时信号,产生信号了不用立即处理
34-64是实时信号,信号产生了就必须处理。
我们学习的是1-31的普通信号。
4.信号的管理
那么进程又是如何管理信号的呢?
是在进程的PCB中
eg:
task_struct {
uint32_t sig;//位图 0000 0000
…
}
位图的内容表示有没有该信号,位图的位置表示是哪一个信号,由于PCB是在内核数据结构,所以只有OS有资格修改位图,OS是进程的管理者,进程的所有属性的获取和设置只能又操作系统来设置,因此无论信号怎么产生,最终一定是OS帮我们进行信号的设置
下面我将从三个部分:信号产生前,信号产生中,信号产生后来叙述进程间信号
二.信号产生前
上述可以了解到,信号在OS中是由位图表示的,所以信号的产生OS发送给进程的时候不如说是写入信号。
1.验证键盘是可以产生信号的
sighandler_t signal(int signum, sighandler_t handler);
对信号设置回调
#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
验证Ctrl + C
是2号信号,我们先对
-定义捕捉动作当我们在键盘上按Ctrl+C
的时候,如果调用了我们的自定义函数,验证成功!
void handler(int signo)
{cout << "i am a process ,我获取了一个信号: " << signo << endl;
}
int main()
{signal(SIGINT, handler);sleep(3);cout << "自定义信号捕捉函数设置完毕" << endl;while (true){cout << "我是一个正在运行的进程" << endl;sleep(1);}return 0;
}
signal(SIGINT, handler);
这里不是在调用handler
方法,只有信号产生的时候,才会调用handler
方法.
实验结果如下
因此可以得出结论:Ctrl + C :本质就是给前台进程发送2号信号给目标进程,目标进程默认对2号信号的处理动作就是终止自己,然而现在我们设置了用户对信号的自定义处理动作。
Ctrl + C 产生2号信号
Ctrl +\ 产生3号信号,同样也是终止进程
注意:9号信号是不能设置自定义的,即使设置了,kill -9 PID 照样也可以杀死进程,因此9号信号也叫做管理员信号
2.通过系统调用接口发送信号
int kill(pid_t pid, int sig);
不仅是一个命令,还是一个系统调用接口,表示对某个进程发送某个信号
我自己写一个mykill
进程,该进程是调用了kill
这个函数的,可以得出结论,代码如下
mykill.cc
static void Usage(const string &proc)
{cerr << " Usage :\n\t" << proc << " signo pid " << endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}if (kill(static_cast<pid_t>(atoi(argv[2])), atoi(argv[1])) == -1){cerr << "kill :" << strerror(errno) << endl;}}
myproc.cc
while (true){sleep(1);cout << "我的PID是: " << getpid() << endl;}
int raise(int sig);
给自身发信号
NAMEabort - cause abnormal process terminationSYNOPSIS#include <stdlib.h>void abort(void);//直接终止进程
abort()直接终止进程
3.由软件条件产生信号
NAMEalarm - set an alarm clock for delivery of a signalSYNOPSIS#include <unistd.h>unsigned int alarm(unsigned int seconds);
调用alarm
函数可以设定一个闹钟,也就是告诉内核在seconds
秒之后给当前进程发送SIGALRM
信号,该信号的默认处理动作是终止当前进程。函数的返回值是0或者是设定闹钟时间的剩余秒数。
int cnt = 0;void handler(int signo)
{cout << "i am a process ,我获取了一个信号: " << signo << endl;cout << "cnt: " << cnt << endl;
}
int main()
{signal(SIGALRM, handler);alarm(1);while (1){cnt++;}
}
一秒钟之后就会捕捉SIGALRM
信号
4.硬件异常产生信号
下面先列举两个崩溃的进程
猜想一下:上述两个进程崩溃的本质也是收到了某个信号,验证一下,先将每个信号都设置自定义动作,然后再运行。
进程崩溃的本质:是该进程收到了异常信号,因为硬件异常,而导致OS向目标进程发信号,进而导致进程终止的现象。
除0:
CPU内部,状态寄存器,当我们除0的时候,CPU内的状态寄存器会被设置成为有报错:浮点数越界,CPU内部寄存器(硬件),OS就会识别到CPU内有报错->1,然后OS就会向目标进程发信号,目标进程在合适的时候处理信号,终止进程
越界&野指针
我们在语言层面使用的地址(指针),其实都是虚拟地址->物理地址->物理内存->读取对应的数据和代码,如果虚拟地址有问题,而地址转化的工作是由MMU(硬件)+页表(软件)构成,转化过程就会引起问题->表现在硬件MMU上->OS就会发现硬件出了问题->OS向目标进程发送信号->目标进程在合适的时候处理信号->终止进程
由此可得出结论:我们在C/C++
中除0,内存越界等异常,在系统层面上是被当成信号处理的。
5.总结
- 上面所说的信号的产生,信号的发送,最终都是要由OS来执行的,因为OS是进程的管理者
- 信号不是被立即执行,而是在进程合适的时候
- 信号不是被立即执行的,那么信号就会被记录下来,记录在进程的
PCB
中 - 一个进程在没有收到信号的时候,能否知道自己应该对合法信号做何处理?能,处理方式已经由之前的程序员写在内核了
6.core dump
Coredump叫做核心转储
,是进程在运行时突然崩溃的一个内存快照。
pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);
wait 与waitpid
都有一个status
参数,该参数是一个输出型参数,由OS填充,其他比特位在我之前的博客有提过,当生成Core dump
文件的时候,该标记位会设置成1.
由上可以看出,并不是所有的信号都会生成core
文件的,只有程序自身内部出了问题才会产生core
文件。
int main()
{pid_t id = fork();if (id == 0) // 子进程{int *p = nullptr;*p = 10000; // 野指针问题exit(1);}int status = 0;waitpid(id, &status, 0);printf("exitcode: %d,signo: %d, core dump flag: %d \n", (status >> 8) & 0xff, status * 0x7f, (status >> 7) & 0x1);
}
运行结果
可以子进程的退出信号是11,符合野指针出错信号,但此时的core dump
标记位还是0,为什么呢?
因为我是线上的的云服务器,厂商设置的默认core
文件个数就是0,也就是说禁止生成core
文件。
设置之后此时允许生成core
文件。
但这不是真正的用途,此时我们要生成可调试版本的程序。
为什么线上的云服务器将core dump文件关闭?
因为云服务器上的产品一般都是release
版本,但是我们生成的core
文件是可调式版本的,并且如果线上的产品挂掉了,最重要的不是找bug而是重启,并且一旦服务挂掉了,会直接重启,eg一秒钟重启一万次的话,每次都有core
文件的话,此时磁盘占据大量的文件,磁盘被打满了会危急到操作系统,很危险!
总结一下:
信号从产生到递达之间的状态称为信号未决
进程可以选择阻塞某个信号,当进程阻塞信号时,信号无法被抵达
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
阻塞和忽略是不同的,只要信号被阻塞那么信号即使产生了也不会被递达,忽略是信号处理的一个动作
信号产生中
1.信号在内核中的表示
信号在内核中的表示
信号在内核中task_struct指向三张表,三张表都是用
位图表示的。
pending:未决信号集,表示该信号是否产生
block:阻塞信号集,表示该信号是否被阻塞
handler:指向的是每个信号的自定义函数.
以上述的SIGQUIT
为例,此时该信号未产生,一旦产生该信号,他的处理动作是用户的自定义函数sighandler
,但是由于此时该信号被阻塞了,此时该信号不会抵达,除非接触对该信号的阻塞,才会抵达。
同时,如果一个信号最初未被阻塞,但是在某信号抵达之前,也可以说是该信号正在处理的时候,如果继续产生该信号,该信号也还是只会被记录一次,实时信号在抵达之前可以产生多次,这里不讨论。
2.信号集操作函数
从上图来看,每个信号只有一个bit
的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也一样,因此,未决和阻塞标志可以用相同的数据类型sigset_t
来储存。
sigset_t
类型对于每种信号用一个bit
表示有效
或无效
,这个类型内部如何储存这些bit
则依赖于系统实现,我们不必关心,我们只要会使用如下几个函数就可以了。
typedef __sigset_t sigset_t;
......
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;
根据上述源码可以看到sigset_t
的实现与系统自身有关,所以我们不必关心。
int sigemptyset(sigset_t *set)
初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set)
使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号
int sigaddset(sigset_t *set, int signo)
该函数允许将一个指定的信号添加到一个自定义信号集中,也就是将该信号的标准为设为1,表示阻塞该信号。
int sigdelset(sigset_t *set,int signo)
与上述函数相反,表示解除该信号的阻塞
int sigismember(const sigset_t *set, int signo)
判断一个信号集的有效信号中是否包含某种信号,也就是检查是否屏蔽该信号,如果包含则返回1,反之0
int sigpending(sigset_t *set);
获取当前进程(谁调用,获取谁)的pending信号集,通过set
参数传出,调用成功返回0,失败返回-1
int sigpromask(int how, const sigset_t *set, sigset_t *oset
成功返回0,失败返回-1
若oset非空:当前进程的信号屏蔽字通过oset传出
set非空:更改进程的信号屏蔽字
how:指示如何更改
如果oset 和 set都非空,则将原来的信号屏蔽字备份到oset,然后根据set和how参数更改信号屏蔽字。 假设当前的信号屏蔽字为mask,下述说明了how参数的可选值
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号 |
---|---|
SIG_UNBLOCK | set包含了从当前信号屏蔽字接触的·信号 |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值 |
V1.1版本的代码先指示将2号信号添加到信号屏蔽字中
预期结果:初始的pending信号集都是0,当我们向进程发送2号信号后,pending信号集中表示2号信号的比特位变成1
#include <unistd.h>
#include <iostream>
#include <signal.h>
using namespace std;void showpending(sigset_t *pendings)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(pendings, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}
void handler(int signo)
{cout << "我是一个进程,刚刚获得了一个信号: " << signo << endl;
}
int main()
{// 2.屏蔽掉2号信号sigset_t bsig, obsig;sigemptyset(&bsig);sigemptyset(&obsig);// 2.1添加2号信号到信号屏蔽字中sigaddset(&bsig, 2);// 2.2 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽2号信号sigprocmask(SIG_SETMASK, &bsig, &obsig);cout << "pid: " << getpid() << endl;signal(SIGINT, handler);// 1.不断获取当前进程的pending信号集sigset_t pendings;int cnt = 0;while (true){// 1.1清空信号集sigemptyset(&pendings);// 1.2获取当前进程pending信号集(谁调用就获取谁)if (sigpending(&pendings) == 0){// 1.3打印一下当前进程的pending信号集sleep(1);showpending(&pendings);cnt++;}cout << "cnt: " << cnt << endl;}return 0;
}
V2.0
先将所有的信号都屏蔽,在20秒之后解除2号和3号信号
下面只贴部分更改的代码
sigset_t bsig, obsig;sigemptyset(&bsig);sigemptyset(&obsig);// 2.1添加1-31号信号到信号屏蔽字中for (int sig = 1; sig < 32; sig++){sigaddset(&bsig, sig);}// 2.2 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽2号信号sigprocmask(SIG_SETMASK, &bsig, &obsig);cout << "pid: " << getpid() << endl;signal(SIGINT, handler);signal(3, handler);// 1.不断获取当前进程的pending信号集sigset_t pendings;int cnt = 0;while (true){// 1.1清空信号集sigemptyset(&pendings);// 1.2获取当前进程pending信号集(谁调用就获取谁)if (sigpending(&pendings) == 0){// 1.3打印一下当前进程的pending信号集sleep(1);showpending(&pendings);cnt++;}if (cnt == 20){// sigprocmask(SIG_SETMASK,&obsig,nullptr); 直接用该方法可以直接解除所有信号集sigset_t sigs;sigemptyset(&sigs);sigaddset(&sigs, 2);sigaddset(&sigs, 3);sigprocmask(SIG_UNBLOCK, &sigs, nullptr);}cout << "cnt: " << cnt << endl;}
根据上述结果可以看出,当我们将信号添加到信号集之后,我们向进程发送信号时,此时代表该信号的比特位由0 --> 1
解除信号屏蔽之后,就会重新由1 --> 0
但是根据结果可以看出 9号信号即使被屏蔽了还是可以杀死进程
信号产生后
1.了解内核态和用户态
上述提到信号产生后,OS系统是在什么时候处理信号呢?
实在合适的时候,那合适的时候具体是什么时候呢?
当 当前进程从内核态切换回用户态的时候进行信号的检测与处理!
每个进程都有自己的task_struct
指向其虚拟地址,虚拟地址到物理地址的转化是通过页表实现的,而每个进程对于自己的用户空间3G是独立的,还有一份公共的内核页表,如下所示.
那么OS在不在内存中被加载?答案是肯定的
无论进程如何切换,我们都可以找到内核的代码和数据,前提是你要有足够的权利进行访问!
那么当前的进程如何具备权利访问内核页表乃至访问内核数据呢?
要进行身份切换。我们要让OS知道此时访问数据的是内核还是页表
CPU内有对应的状态寄存器CR3寄存器,当比特位是0的时候表示内核态,当比特位是3的时候表示用户态:
用户态:只能访问用户级页表
内核态:既能访问内核级页表也能访问用户级页表
内核态相比于用户态拥有更高的权限
那么一般什么时候会从用户态切换回内核态呢?
- 系统调用的时候
- 时间片到了进行进程间切换等
2.内存如何实现信号的捕捉
我们必须要了解一个知识:
我们的程序,会无数次直接或者间接的访问系统级软硬件资源(管理是OS),本质上,你并没有去操作这些软硬件资源,而是必须通过OS–>无数次陷入内核(1.切换身份 2.切换页表) -->调用内核的代码–>完成访问的动作–>结果返回给用户(1.切换身份 2.切换页表)–>得到结果
eg:
while(1);
仅仅是这一行代码存在从用户态切换成内核态吗?
一定是有的,因为每个进程都有自己的时间片,当时间片到了,需要转换成内核态然后更换内核级页表 -->为了保护上下文,执行调度算法–>切换新的进程–>恢复新进程的上下文–>再切换成用户态–>CPU执行的就是新进场的代码!
下面一个场景,当我们调用完系统调用之后,返回内核态时,检测出了错误;
快速记忆
3.sigaction
该函数可以读取和修改指定信号相关联的处理动作,成功返回0,失败返回-1.
#include<signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
参数解析
signum:指定信号编号
act:非空,根据指针act修改该信号的处理动作
oldact:非空,通过其传出该信号原来的处理动作
act和oldact都是sigaction结构体
上述有该结构体的具体组成
sa_handler:表示该信号的处理动作
当某个信号正在被处理时,内核会自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复成原来的信号屏蔽字,这样的话,当前进程正在被处理时,如果这个信号再次产生,该信号会被阻塞直到当前信号处理结束。
如果在调用信号处理函数时,除了希望自动屏蔽当前信号,还希望自动屏蔽其他信号,则用sa_mask
字段说明这些需要额外屏蔽的信号,当信号处理函数返回时恢复成原来的状态
sa_flags设为0.
实验一:先不给sa_mask添加信号
void handler(int signo)
{cout << "我是一个进程,刚刚获得了一个信号: " << signo << endl;sigset_t pending;// 此时会永远在处理某个信号while (true){sigpending(&pending);for (int i = 1; i <= 31; i++){if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;sleep(1);}
}
int main()
{cout << "my pid: " << getpid() << endl;struct sigaction act, oldact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);// sigaddset(&act.sa_mask, 3);sigaction(2, &act, &oldact);while (true){cout << "main running" << endl;sleep(1);}return 0;
}
实验二:给sa_mask添加信号
sigaddset(&act.sa_mask, 3);
相关文章:

Linux——进程间信号(超级详解!!)
索引 一.初始信号1.什么是信号2.前后台进程3.信号的种类4.信号的管理 二.信号产生前1.验证键盘是可以产生信号的2.通过系统调用接口发送信号3.由软件条件产生信号4.硬件异常产生信号5.总结6.core dump 信号产生中1.信号在内核中的表示2.信号集操作函数 信号产生后1.了解内核态和…...

C++ STL库的介绍和使用
文章目录 C STL库的介绍和使用STL六大组件算法的分类迭代器 一个简单的例子容器和自定义类型容器嵌套容器常用容器stringvectordequestackqueuelistset/multisetpairmap/multimap 容器的使用时机函数对象(仿函数)谓词内建函数对象适配器bind2nd和bind1st…...

Excel数学、工程和科学计算插件:FORMULADESK Studio
如果 Excel 是您的武器 - 让我们磨砺您的剑!为整天使用 Excel 的人们提供创新的 Excel 加载项,你需要这个 FORMULADESK Studio。。。 Excel 插件为任何使用 Excel 执行数学、工程和科学计算的人提供了必备工具。 * 将公式视为真正的数学方程 * 为您的公…...

大规模 Spring Cloud 微服务无损上下线探索与实践
文章目录 什么是无损上下线?大规模 Spring Cloud 微服务架构实现无损上下线的挑战无损上下线的实践1. 使用负载均衡器2. 使用数据库迁移工具3. 动态配置管理4. 错误处理和回滚 未来的趋势1. 容器编排2. 服务网格3. 自动化测试和验证 结论 🎉欢迎来到云原…...

【LeetCode】剑指 Offer 54. 二叉搜索树的第k大节点
题目: 给定一棵二叉搜索树,请找出其中第 k 大的节点的值。 示例 1: 输入: root [3,1,4,null,2], k 13/ \1 4\2 输出: 4 示例 2: 输入: root [5,3,6,2,4,null,null,1], k 35/ \3 6/ \2 4/1 输出: 4 限制: 1 ≤ k ≤ 二叉搜索树…...

C++设计模式_03_模板方法Template Method
文章目录 1. 设计模式分类1.1 GOF-23 模式分类1.2 从封装变化角度对模式分类 2. 重构(使用模式的方法)2.1 重构获得模式 Refactoring to Patterns2.2 重构关键技法 3. “组件协作”模式4. Template Method 模式4.1 动机( Motivationÿ…...

【LeetCode-中等题】79. 单词搜索
文章目录 题目方法一:递归 回溯 题目 方法一:递归 回溯 需要一个标记数组 来标志格子字符是否被使用过了先找到word 的第一个字符在表格中的位置,再开始递归递归的结束条件是如果word递归到了最后一个字符了,说明能在矩阵中找到单…...

揭秘iPhone 15 Pro Max:苹果如何战胜三星
三星Galaxy S23 Ultra在我们的最佳拍照手机排行榜上名列前茅有几个原因,但iPhone 15 Pro Max正在努力夺回榜首——假设它有一个特定的功能。别误会我的意思,苹果一直在追赶三星,因为它的iPhone 14 Pro和14 Pro Max都表现强劲。尽管如此&#…...

分布式秒杀方案--java
前提:先把商品详情和秒杀商品缓存redis中,减少对数据库的访问(可使用定时任务) 秒杀商品无非就是那几步(前面还可能会有一些判断,如用户是否登录,一人一单,秒杀时间验证等࿰…...

高频golang面试题:简单聊聊内存逃逸?
文章目录 问题怎么答举例 问题 知道golang的内存逃逸吗?什么情况下会发生内存逃逸? 怎么答 golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。…...

【2023年数学建模国赛C题解题思路】
第一问 要求分析分析蔬菜各品类及单品销售量的分布规律及相互关系。该问题可以拆分成三个角度进行剖析。 1)各种类蔬菜的销售量分布、蔬菜种类与销售量之间的关系;2)各种类蔬菜的销售量的月份分布、各种类蔬菜销售量与月份之间的相关关系&a…...

Jenkins+Allure+Pytest的持续集成
一、配置 allure 环境变量 1、下载 allure是一个命令行工具,可以去 github 下载最新版:https://github.com/allure-framework/allure2/releases 2、解压到本地 3、配置环境变量 复制路径如:F:\allure-2.13.7\bin 环境变量、Path、添加 F:\a…...

yo!这里是进程控制
目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果(status介绍) 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…...

多线程快速入门
线程与进程区别 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里…...

Redis 7 第七讲 哨兵模式(sentinal)架构篇
哨兵模式 哨兵巡查监控后台master主机是否故障,如果出现故障根据投票时自动将某一个从库转换成新的主库,继续对外服务。 作用 1. 监控redis运行状态,包括master和slave 2. 当master down机,能自动将salve切换成新的master 应用场景 主从监控监控主从redis库运行的状态…...

laravel框架系列(一),Dcat Admin 安装
介绍 Laravel 是一个流行的 PHP 开发框架,它提供了一套简洁、优雅的语法和丰富的功能,用于快速构建高质量的 Web 应用程序。 以下是 Laravel 的一些主要特点和功能: MVC 架构:Laravel 使用经典的模型-视图-控制器(MV…...

Linux:工具(vim,gcc/g++,make/Makefile,yum,git,gdb)
目录 ---工具功能 1. vim 1.1 vim的模式 1.2 vim常见指令 2. gcc/g 2.1 预备知识 2.2 gcc的使用 3.make,Makefile make.Makefile的使用 4.yum --yum三板斧 5.git --git三板斧 --Linux下提交代码到远程仓库 6.gdb 6.1 gdb的常用指令 学习目标: 1.知道…...

小节1:Python字符串打印
1、字符串拼接 用可以将两个字符串拼接成一个字符串 print("你好 " "这是一串代码") 输出: 2、单双引号转义 当打印的字符串中带有引号或双引号时,使用\或\"表示 print("He said \"Let\s go!\"") 输…...

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策
2023国赛C题:蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单,实际上23题非常的难,编程难度非常的大,第二题它是一个典型的动态规划加仿真题目,我们首先要计算出销量与销售价格,批发价格之间的…...

数据可视化工具中的显眼包:奥威BI自带方案上阵
根据经验来看,BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成,且大多数时候方案都需从零开始,反复调整,会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心,将17年经验凝聚成标准化、…...

LeetCode算法心得——生成特殊数字的最少操作(贪心找规律)
大家好,我是晴天学长,这是一个简单贪心思维技巧题,主要考察的还是临场发挥的能力。需要的小伙伴可以关注支持一下哦!后续会继续更新的。 2) .算法思路 0 00 50 25 75 末尾是这两个的才能被45整除 思路:分别找&#x…...

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献
【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献 1 题目 1.1 问题背景 多波束测深系统是利用声波在水中的传播特性来测量水体深度的技术,是在单波束测深的基础上发展起来的,该系统在与航迹垂直的平面内一次能发射出数十个乃至上百个…...

如何处理异步编程中的回调地狱问题?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 解决回调地狱问题的方法⭐使用 Promise⭐使用 async/await⭐ 使用回调函数库⭐模块化⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端…...

什么是Lambda表达式?
Lambda表达式是Java 8引入的一个重要特性,用于简化函数式编程中的匿名函数的定义和使用。它可以被视为一种轻量级的匿名函数,可以作为参数传递给方法或存储在变量中。 Lambda表达式的语法形式如下: (parameters) -> expression 或 (para…...

公式trick备忘录
增大不同class feature之间的距离用hinge loss 相关, similarity learning, svm https://www.youtube.com/watch?vQtAYgtBnhws https://www.youtube.com/watch?vbM4_AstaBZo&t286s...

向量数据库Milvus Cloud核心组件再升级,主打就是一个低延迟、高准确度
支持 ScaNN 索引 Faiss 实现的 ScaNN,又名 FastScan,使用更小的 PQ 编码和相应的指令集可以更为友好地访问 CPU 寄存器,从而使其拥有优秀的索引性能。该索引在 Cohere 数据集,Recall 约 95% 的时候,Milvus 使用 Knowhere 2.x 版本端到端的 QPS 是 IVF_FLAT 的 7 倍,HN…...

ELK框架Logstash配合Filebeats和kafka使用
ELK框架Logstash配合Filebeats和kafka使用 本文目录 ELK框架Logstash配合Filebeats和kafka使用配置文件结构input为标准输入,output为标准输出input为log文件output为标准输出output为es input为tcpspringboot配置logstash配置 input为filebeatsfilebeats配置logsta…...

后端面试话术集锦第 十二 篇:java基础部分面试话术
这是后端面试集锦第十二篇博文——java基础部分面试话术❗❗❗ 1. String类中常用的方法 split():把字符串分割成字符串数组 indexOf():从指定字符提取索引位置 trim():去除字符串两端空格 replace():替换 hashCode():返回此字符串的哈希码 subString():截取字符串 equa…...

【广州华锐互动】电厂三维数字孪生大屏的功能和优势
在工业互联网的背景下,电厂三维数字孪生大屏系统正在逐渐成为电力行业的重要技术。通过创建电厂的虚拟模型,这个数字孪生系统可以实现对实际电厂的实时监控,预测维护需求,优化运营效率,甚至在某些情况下,能…...

es6解构用法
一: 解构数组 二:解构对象 一: 解构数组 原理:模式(结构匹配), 索引值相同的完成赋值 总结:位置对应 二:解构对象 原理:模式(结构匹配), 属性名相同的完成赋值 {}{} 对象结构赋值的应用 常用的就以上两种 &#…...