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

linux入门---信号的理解

目录标题

  • 如何理解计算机中的信号
  • 如何查看计算机中的信号
  • 初步了解信号的保存和发送
  • 如何向目标进程发送信号
    • 情景一:使用键盘发送信号
    • 情景二:系统调用发送信号
    • 情景三:硬件异常产生信号
    • 情景四:软件条件产生信号
  • 核心转储
  • 信号的两个问题
    • 问题一
    • 问题二

如何理解计算机中的信号

我们首先通过生活中的信号来理解一下计算机中的信号,生活中会遇到很多的信号,比如说运动会上的发令枪,晚上睡觉前定的闹钟,过马路开车需要查看的红绿灯,以及手机在收到信息发出的响声这也是信号,手机发出的响声是一个信号,并且我们人是可以识别信号的,这里的识别指是人能够认识这个信号,并且我们能因为这个信号而做出对应的行为,那我们为什么能够识别红绿灯呢?为什么当听到手机发出响声之后知道是手机接收到一些信息的呢?原因是有人教育过你有经验告诉过你,当这些信号产生的时候意味着一些事情要被处理,通过教育的手段和以往经验的积累,让你在大脑里面记住了一些信号的属性(为什么会产生这样的信号?)和与之对应处理行为(我们人该如何处理这个信号),比如说我们开车的过程中发现红灯亮了起来,那我们就得在合适的地方将车停下来了,红灯就是一个信号,将车停下来就是对这个信号的处理,那为什么会有红灯这个信号呢?原因是为了维持社会交通的便利,那为什么我们知道红灯是一个信号遇到红灯是就得停车呢?原因是从小爸爸妈妈和老师就教育我们遇到红灯的时候就得停止向前运动直到红灯变成了绿灯,这是一个教育的过程让我们知道了红灯是一个信号,以及信号的处理行为,再比如说手机发出的响声就是一个信号,当手机发出声响的时候我们就知道有人或者有软件给我们发了消息,我们就可以打开手机对消息进行查看,那么我们是如何知道手机发出声响是一个信号的呢?原因是在之前使用手机的过程中每当有人或者有软件给我们发消息时手机发出声响,即使我们不用手机将他放到一边当收到消息的时候他也会发出声响,这样我们人就会积攒经验知道手机发出声响就意味着我们收到了消息,该消息可能要被处理,那么这就是我们为什么会认识信号的原因,那接收到了信号就一定得立即对其进行处理吗?答案是不是的,因为信号时可以随便产生的,但是在信号产生的时候我们可能有更重要的事情要做,比如说手机叮咚响了一声我们就必须得马上打开手机进行查看吗?对吧!没必要我们可以选择忽略这个信号直到我们忙完了再查看这个信号都不晚。信号到来的时候我们不一定要立马处理这个信号,因为我们当前可能得做着更重要的事情,所以信号产生到信号被处理的过程中存在一个时间窗口,所以在这个时间窗口里面我们必须得记住这个信号,那么如何保存信号就是我们后面要学习的内容,我们知道什么是信号是因为有人教育过我们,那对信号的处理是固定的吗?我们可不可以直接忽略掉这个信号,比如说红灯亮起的时候我们依然选着向前移动,手机不停地响但是我们依然选继续干正在做的事,答案是可以的在处理信号的时候我们可以选择直接忽略掉信号,那么同样的道理我们可以修改以往对信号处理的行为吗?比如说之前有人一直教育我们遇到红灯就得停止向前运动,这是一个默认的信号处理行为,我们可以对这个行为进行修改,当红点亮了我们也对对其进行做出行为,但是这个行为是将运动向前变成向后,同样的道理当手机响的时候默认动作是打开手机并查看消息,那么我们能对这个行为进行修改,当手机响了之后我们可以直接将手机进行关机,那么这就是对信号默认行为的修改,看到这里想必大家应该能够理解信号,但是大家心里一定存在个疑问,人可以被教育可以从经验中总结结果,那计算机又是如何认识信号的呢?进程识别信号的方式就是:认识信号+处理信号,而信号是发送给进程的,那进程如何识别信号的呢?人能够认识信号是因为人受到过教育,进程能够认识信号则是因为进程本身是程序员编写的属性和逻辑的集合,这些都是程序员编码完成的,所以我们写的程序能够识别到信号,当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定会被立即处理,所以进程本身必须要有对信号的保存能力,进程在处理信号的时候一般有三种动作(默认,自定义,忽略),我们把处理信号的动作称为信号捕捉。那么接下来我们就来逐步逐步的理解信号。

如何查看计算机中的信号

kill -l可以查看所有的信号
在这里插入图片描述
数字就表示信号的编号,数字后面的名称就是数字对应的宏,所以未来我们既可以使用编号也可以使用宏来操控信号,通过观察我们可以看到一共有62个信号,我们把1-31称为普通信号 34-64称为实时信号(这个不学)。

初步了解信号的保存和发送

在前面的介绍中我们知道信号发送之后可能不会立即处理,所以得将信号保存起来,那信号应该保存在哪里呢?答案是保存在task_struct里面,那该保存什么内容呢?答案是保存是否收到了指定的信号,如果收到了指定的信号就保存1没有收到就保存0,那么这里有31个信号,所以在task_struct里面就有一个unsigned int signal来存储是否收到了信号,比特位的位置代表信号的编号,比特位的内容,代表是否收到了该信号,0表示没有,1表示有,既然存储信号的方法是通过PCB中的一个位图来进行存储,那么发送信号的本质就是修改PCB中的信号位图,将位图中的某个比特位由0变成1,PCB是内核维护的数据结构对象,所以PCB的管理者是操作系统,那谁有权利修改PCB的内容呢?答案是操作系统,所以无论未来我们学习了多少种发送信号的方式,本质都是通过操作系统向目标进程发送信号,所以操作系统必须要提供发送信号处理型号的相关系统调用,我们使用的kill命令底层一定是调用了对应的系统调用,比如说我们提供了下面这样的程序:

#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{while(1){cout<<"我是一个死循环的进程"<<endl;sleep(1);}return 0;
}

生成可执行文件
在这里插入图片描述
然后运行一下程序就可以看到下面这样的场景:
在这里插入图片描述
可以看到这里在死循环的打印语句,那么想要结束这个进程我们就可以按下键盘上的ctrl+c,然后我们就可以程序终止了:
在这里插入图片描述
那么ctrl+c就是一个热键—本质就是一个组合键,操作系统会将这个组合键识别成成为二号信号发送给进程,通过man 7 signal可以查看所有信号的信息和与之对应的默认操作:
在这里插入图片描述
signal是信号的名称,value表示信号的值,action表示信号的动作,comment是信号的描述,通过这张图片我们便可以知道二号信号的名称就是sigint,动作是Term对这个信号的描述就是用键盘来中断当前运行的进程,所以我们可以看到程序停止向屏幕上进行打印了,那么为了验证这一点我们可以用signal函数来进行判断,signal函数的作用就是修改信号的处理动作
在这里插入图片描述
signal函数的第一个参数表示信号的编号也就是你要对哪个信号进行修改,第二个参数的类型为sighandler_t通过上面显示我们知道sighandler是一个重命名类型,该类型的本质就是一个函数指针,函数的返回类型是void参数就是一个整形,该参数的作用就是将信号原来的默认动作修改成函数指针指向的函数,比如说下面的程序:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}int main()
{signal(2,handler);while(1){cout<<"我是一个死循环的进程,我的pid为:"<<getpid()<<endl;sleep(1);}return 0;
}

运行的结果如下:
在这里插入图片描述
可以看到程序的运行是没有问题的,然后当我们按下ctrl c给进程发送信号的时候程序并不会终止,而是执行了我们之前设置好的内容,打印了一句话:
在这里插入图片描述
打印完这句话后接着执行死循环:
在这里插入图片描述
那么这也就验证了,当我们使用键盘输入ctrl c的时候本质上就是给当前的进程发送2号信号,2号信号的作用就是将当前的程序终止。

如何向目标进程发送信号

情景一:使用键盘发送信号

通过前面的学习我们知道使用组合键ctrl c可以向前台进程发送信号从而终止进程:
在这里插入图片描述
那么这里还有一个ctrl \的快捷键,他的作用就是给前台进程发送3号信号从而终止进程,那么这里的代码如下:
在这里插入图片描述
那么这就是通过键盘向进程发送信号。

情景二:系统调用发送信号

1.kill函数
我们学习的第一个函数就是kill,该函数的声明形式如下:
在这里插入图片描述
第一个参数表示向哪个进程发送信号,第二个参数表示向进程发送几号信号,我们再看看这个函数的返回值:
在这里插入图片描述
可以看到当信号发送成功就返回0,如果失败就返回-1,那么这就是该函数的作用,那么接下来我们就可以写一个人程序,专门用来给指定的进程发送信号,程序使用的方法就是运行程序的时候传递两个参数第一个参数表示进程的pid第二个参数用来表示发送的具体信号比如说这样:./myproc 进程的pid 信号的编号,那么我们的程序就可以这样设计,因为运行程序的时候会传递参数,所以该程序中的main函数得包含两个参数:

#include<iostream>
using namespace std;
int main(int argc,char*argv[])
{}

然后在程序的开始我们得判断一下该程序的使用是否正确,如果传递argc的值不等于3的话我们就得调用函数来告诉使用者该程序的正确使用方法,所以该函数就得有一个参数用来告诉使用者是哪个进程的使用方法:

#include<iostream>
#include<string>
using namespace std;
void usage(const string& proc)
{cout<<"\nusage:"<<proc<<"pid signo"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=3){usage(argv[0]);exit(1);}
}

然后我们就可以通过main函数的第二个参数来获取对应的pid和信号,然后将其转换成为整形最后就可以调用kill函数发送对应的信号即可:

#include<iostream>
#include<string>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
using namespace std;
void usage(const string& proc)
{cout<<"\nusage:"<<proc<<"pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{if(argc!=3){usage(argv[0]);exit(1);}pid_t pid=atoi(argv[1]);int signo=atoi(argv[2]);kill(pid,signo);return 0;
}

那么接下来我们就可以先测试一下myproc.cc文件是否运行正常:
在这里插入图片描述
可以看到当前的程序是可以正常运行的,那么这里我们就可以先运行一下死循环程序:
在这里插入图片描述
然后再打开一个回话,并运行myproc程序并传递20261和2号信号:
在这里插入图片描述
然后就可以看到程序自动的终止了,同样的道理我们还可以发送3号信号给进程,这样我们就可以看到打印出来quit的字样:
在这里插入图片描述
那么这就是kill函数的用法。

第二个:raise
该函数的声明如下:
在这里插入图片描述
这个函数也可以给进程发送信号,但是他不能指定任意进程,他只能给自己发送对应的信号,比如说下面的代码:

int main()
{int cnt=1;while(true){cout<<"cnt的值为: "<<cnt<<endl;cnt++;if(cnt==4){raise(2);}}return 0;
}

那么这里我们就可以看到屏幕上打印了3句话话之后就自动的结束了进程,运行的结果如下:
在这里插入图片描述
那么这就是raise函数的用法。

第三个:abort
kill函数能够对任意的进程发送任意的信号,raise函数能够给本进程发送任意的信号,那么abort函数就只能给本进程发送指定的信号也就是6号新号,我们来看看这个函数的介绍:
在这里插入图片描述
我们可以看看6号新号的名字:
在这里插入图片描述
可以看到6号信号对应的宏就是SIGABRT,那么这里就不再介绍,大家理解了即可。

情景三:硬件异常产生信号

信号的产生,不一定非得用户显示的发送,比如说除0会终止进程的时候会终止进程,比如说下面的代码:

int main()
{int i=10;int j=0;int c=i/0;while(true){cout<<"如果没有收到信号就会死循环"<<endl;}return 0;
}

将程序运行起来便可以看到下面的场景:
在这里插入图片描述可以看到这里没有死循环的打印语句,而是报出了异常的内容,那么这就说明当出现除0情况是进程是收到信号的,那这个信号是谁发送的呢?答案是当前进程会受到来自操作系统的信号并且是8号SIGFPF信号,那么这里我们可以使用signal函数修改一下对应的8号信号,修改的操作就是不终止信号并输出信息:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}int main()
{signal(8,handler);int i=10;int j=0;int c=i/0;while(true){cout<<"如果没有收到信号就会死循环"<<endl;}return 0;
}

程序的运行代码如下:
在这里插入图片描述
可以看到出现/0错误的时确实会收到8号新号,并且会不停的执行8号信号的处理方法,那么这里就存在一个问题我们只除了一次0,为什么会不停的捕捉呢?操作系统又是如何得知应该给当前进程发送8号信号的呢?那么要想解决这里的问题我们就得了解一下cpu的构造,cpu中存在着大量的寄存器,这些寄存器会存储大量的数据比如说各种表达式或者函数的计算结果,寄存器不仅得存储计算的结果还得保存表达式的计算状态,所以有一种寄存器称为状态寄存器,该寄存器中存在一个比特位称为溢出标记位,10除以0得到的结果是无穷大,那么无穷大这个结果就会导致状态寄存器中的溢出由0变成了1,虽然这次表达式计算出现了问题,但是寄存器中依然会存储一些数据(这里就体现出溢出标记位的作用,因为都会保存一些值,那么这个值是否是对的就有状态寄存器来决定),cpu出现运算异常,那么操作系统是肯定要知道,所以操作系统就可以通过状态寄存器上的标记位知道发生了什么异常?然后再查询当前运行的是哪个进程以及进程运行的位置这样便知道是哪个进程的哪里出现了异常,然后操作系统修改标记位并发送信号,那为什么会一直打印信息呢?我们之前说过收到信号进程不一定会退出,没有退出的话就说明该进程还会被调度,cpu内部的寄存器只有一份,但是寄存器中的内容属于当前进程的上下文,一旦出现了异常了我们修改之后的动作有能力或者行为来修改这个问题吗?答案是没有的,所以进程会被多次切换,与之对应的寄存器的内容就会被多次保存或者回复,所以每一次回复的时候操作系统就会识别到状态寄存器中的溢出标记位,然后修改进程pcb中的标记位并发送信号所以就可以看到当我们对8号信号的动作进行修改时会不停执行8号信号的处理动作。同样场景也会出现在对野指针解引用上,当我们对野指针进行解引用时也会出现崩溃,结束进程,比如说对nullptr地址进行解引用,那么这个时候就会发送11号信号,那操作系统为什么会知道出现野指针的错误呢?答案是虚拟地址转换到物理地址需要页表加上mmu,mmu是内存管理单元这是一个硬件集成在cpu上,当我们访问0号地址时,虚拟地址空间会拒绝我们的访问,然后mmu就会因为我们的越界访问出现了异常,当一个硬件发生了异常时,操作系统便会得知在地址转换的过程中出现了异常,然后操作系统就会发送11号信号,按摩这就是硬件层面上的异常。

情景四:软件条件产生信号

我们之前学习过进程之间的通信,在那里我们知道了管道文件的特点,当管道的读端关闭,写段一直写的时候,操作系统就会通过发送sigpipe信号的方式来终止进程,那么这就是一个经典的软件条件产生的异常,那么这里我们再举一个例子:alarm函数:
在这里插入图片描述

alarm函数的作用就是让程序运行一段时间,时间到了就会发送14号-信号来结束进程,那么alarm的参数就表示在多少秒之后发送信号,所以我们就可以使用闹钟来实现下面这样的代码:

int main()
{alarm(1);//程序1秒后会被信号终止int cnt=0;while(true){cout<<"cnt: "<<cnt++<<endl;}return 0;
}

然后程序的运行结果如下:
在这里插入图片描述
可以看到循环执行了6w多次,那么这段代码的意义就是统计1s左右,我们的计算机能够将数据累加多少次,但是大家不难发现1秒访问6万多次好想有点少,那么这里的原因就是在循环里面要不停的访问外设显示屏,所以运行的速度非常的慢,那么下面我们可以对代码进行修改将cnt改成全局变量,循环里面就不往屏幕上打印数据而是直接对变量++

int cnt=0;
void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;cout<<"cnt的值为: "<<cnt<<endl;exit(1);
}
int main()
{signal(SIGALRM,handler);alarm(1);//程序1秒后会被信号终止while(true){++cnt;}return 0;
}

代码的运行结果如下:
在这里插入图片描述
可以看到这时程序的运行速度就变得十分的快,但是我们为什么把这个函数发出的异常称为软件异常呢?因为闹钟其实就是用软件实现的,任意一个进程都能通过alarm系统调用在内核中设置闹钟,os中可能会存在很多的闹钟,那么操作系统要不要对这些闹钟进行管理呢?答案是要的,管理的方式就是先描述再组织,所以操作系统就会有对应的结构体来描述闹钟,然后操作系统中就会有数据结构来管理这些结构体,比如说链表,堆,操作系统就会不停的检查这些对象中是否有哪些闹钟超时,超时了操作系统就会给对应的进程发送信号,检查闹钟是否超时是操作系统这样的软件来执行的,条件就是现在超时这个条件,所以闹钟就是软件条件。

核心转储

在这里插入图片描述
在信号的行为中我们知道Term和Core都是将进程终止,那这两种终止方式有什么区别呢?term的终止表示是正常的结束,操作系统不会做额外的操作,而core的终止表示不是正常的终止,操作系统会做出额外的操作,比如说SIGFPF就是一个Core类型的终止,当我们写出这样的代码时操作系统就会给我们发送该类型的异常:

int main()
{while(true){int arr[10];arr[10000]=100;}return 0}

运行的结果如下:
在这里插入图片描述
可以看到这里确实发生了段错误,但是在云服务器上如果进程是core终止我们暂时看不到明显的现象,如果想看到我们得打开一个选项:ulimit -a查看云服务器的各种选项
在这里插入图片描述
这里会显示当前云服务器的各种性质,比如说管道的大小,最多能打开的文件个数,其中有个属性为core file size,他表示表示核心转储的大小,如果大小为0的话就表示云服务器默认关闭了核心转储,如果想要看到的话 ulimit -c 1024 表示打开核心转储并设计一个大小为1024的数据块
在这里插入图片描述
再运行一下当前的程序就会出现core dumped的标识

在这里插入图片描述

这个core dumped就是核心转储的意思,并且当前路径下还多出来了一个文件,文件名后面有一串数字表示引起核心转储的进程的pid

在这里插入图片描述

核心转储的意思就是:当进出现异常的时候,我们将进程对应的时刻在内存中的有效数据转储到磁盘中就称为核心转储。我们可以查看一下文件里面的内容:

在这里插入图片描述

文件里面的内容我们是看不懂的,那为什么要有核心转储呢?原因是当进程崩溃的时候,我们想知道进程为什么会崩溃,在哪里崩溃,所以操作系统为了方便我们查看程序崩溃的原因,操作系统就会该进程上下文的数据保存到磁盘中用来支持我们调试,那如何查看这些数据呢?答案是先调试我们的文件
在这里插入图片描述

然后输入core-file 新生成的核心转储文件

在这里插入图片描述

按下回车然后就会显示当前出错的原因:

在这里插入图片描述
大家仔细的观察一下便可以看到上面显示了在程序的第18行出现了异常,第18行的内容为arr[10000]=100;那么这就是核心转储的功能,他可以告诉我们程序在哪出现了异常。

信号的两个问题

问题一

在这里插入图片描述
通过上面的图片我们可以看到很多信号的处理行为都是直接将进程终止,那既然处理的行为都是终止的话那为什么还要分这么多种信号呢?意义是什么呢?答案是不同的信号代表不同的事件,但是对于不同的事件处理的动作可以是一样的,这就意味着虽然都是直接将进程终止,但是出现了不同的信号可以让我们知道是哪些原因导致了信号的发出,然后我们就可以根据这些原因来更改程序,那么这就是不同信号的意义。

问题二

我们能不能对所有的信号都进行自定义捕捉?比如说下面的程序:

void handler(int sign)
{cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}
int main()
{for(int i=0;i<=31;i++){signal(i,handler);}while(true){cout<<"我是一个进程,我的pid为:"<<getpid()<<endl;}return 0;
}

如果我们对所有的信号都做捕捉,那是不是就不能杀掉这个进程了呢?程序运行的结果如下:
在这里插入图片描述
我们之前讲过2号新号和3号新号都可以结束进程,那现在还能够结束掉吗?我们来尝试一下:
在这里插入图片描述
可以看到是不行的,并且其他的一些信号也不行
在这里插入图片描述
那么这是不是就说明这个进程无法被终止掉了呢?不会的我们使用9号信号依然可以将其 终止掉:
在这里插入图片描述
所以操作系统为了能够结束恶意进程是不允许修改某些信号的处理方法的我们把这样信号称为管理员信号,那么这就是本篇文章的全部内容希望大家能够理解。

相关文章:

linux入门---信号的理解

目录标题 如何理解计算机中的信号如何查看计算机中的信号初步了解信号的保存和发送如何向目标进程发送信号情景一&#xff1a;使用键盘发送信号情景二&#xff1a;系统调用发送信号情景三&#xff1a;硬件异常产生信号情景四&#xff1a;软件条件产生信号 核心转储信号的两个问…...

nn.Linear(d, num_units, bias=True)设置bias和不设置bias有什么区别?

nn.Linear(d, num_units, biasTrue)是PyTorch中定义的一个全连接线性层。其中&#xff0c;d是输入特征的数量&#xff0c;num_units是输出特征的数量&#xff0c;而bias参数决定是否在这个线性变换中添加一个偏置项。 设置biasTrue与biasFalse的区别如下&#xff1a; 数学表示…...

代码随想录 Day10 栈与队列 LeetCode T239 滑动窗口的最大值 T347 前K个高频元素

简要介绍一下单调队列和优先级队列的不同 元素顺序的处理&#xff1a;单调队列中&#xff0c;元素的顺序是单调的&#xff0c;也就是说&#xff0c;队列中的元素按照特定的单调性&#xff08;递增或递减&#xff09;排列。这种特性使得单调队列在处理一些问题时非常高效&#…...

vue/自定义指令

需求&#xff1a; 页面有个input元素&#xff0c;现在要鼠标光标聚焦在上面&#xff0c;让每个页面上的标签都可以聚焦光标&#xff0c;比如&#xff0c;从A页面跳转到B页面的时候&#xff0c;我们依然要聚焦。如果要一遍遍地操作dom就会很麻烦。 这个时候&#xff0c;为了方便…...

借用binlog2sql工具轻松解析MySQL的binlog文件,再现Oracle的闪回功能

借用binlog2sql工具轻松解析MySQL的binlog文件 简介依赖配置用户权限选项配置案例&#xff1a;误UPDATE表数据回滚binlog2sql VS mysqlbinlog 看腻文章了就来听听视频演示吧&#xff1a;https://www.bilibili.com/video/BV1Zj411k7VW/ 简介 binlog2sql是美团大众点评开源的一…...

一次解决Pytorch训练时损失和参数出现Nan或者inf的经历

目前在做实验&#xff0c;参考了一个新的网络架构之后发现训练时损失出现Nan&#xff0c;参数了出现了inf的情况&#xff0c;先说说我的排查经历。 首先肯定是打印损失&#xff0c;损失是最容易出现Nan的&#xff0c;有各种原因&#xff0c;网上也有很多解决办法&#xff0c;我…...

【python入门篇】列表简介及操作(2)

列表是什么&#xff1f; 列表是由一系列按特定顺序排列的元素组成。你可以创建包含字母表中的所有字母、数字 0~9 或所有家庭成员的列表&#xff1b;也可以将任何东西加入列表中&#xff0c;其中的元素之间可以没有任何关系。列表通常包含多个元素&#xff0c;因此给列表指定一…...

数据结构与算法——19.红黑树

这篇文章我们来讲一下红黑树。 目录 1.概述 1.1红黑树的性质 2.红黑树的实现 3.总结 1.概述 首先&#xff0c;我们来大致了解一下什么是红黑树 红黑树是一种自平衡的二叉查找树&#xff0c;是一种高效的查找树。红黑树具有良好的效率&#xff0c;它可在 O(logN) 时间内完…...

js题解(三)

文章目录 柯里化模块乘法改变上下文 柯里化 已知 fn 为一个预定义函数&#xff0c;实现函数 curryIt&#xff0c;调用之后满足如下条件&#xff1a; 1、返回一个函数 a&#xff0c;a 的 length 属性值为 1&#xff08;即显式声明 a 接收一个参数&#xff09; 2、调用 a 之后&a…...

CompletableFuture异步回调

CompletableFuture异步回调 CompletableFutureFuture模式CompletableFuture详解1.CompletableFuture的UML类关系2.CompletionStage接口3.使用runAsync和supplyAcync创建子任务4.设置子任务回调钩子5.调用handle()方法统一处理异常和结果6.线程池的使用 异步任务的串行执行thenA…...

Python中匹配模糊的字符串

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 如何使用thefuzz 库&#xff0c;它允许我们在python中进行模糊字符串匹配。 此外&#xff0c;我们将学习如何使用process 模块&#xff0c;该模块允许我们在模糊…...

PHP图片文件管理功能系统源码

文件图库管理单PHP源码直接解压就能用&#xff0c;单文件&#xff0c;indexm.php文件可以重新命名&#xff0c;上传到需要访问的目录中&#xff0c; 可以查看目录以及各个文件&#xff0c;图片等和下载及修改管理服务。 源码下载&#xff1a;https://download.csdn.net/downloa…...

(枚举 + 树上倍增)Codeforces Round 900 (Div. 3) G

Problem - G - Codeforces 题意&#xff1a; 思路&#xff1a; 首先&#xff0c;目标值和结点权值是直接联系的&#xff0c;最值不可能直接贪心&#xff0c;一定是考虑去枚举一些东西&#xff0c;依靠这种枚举可以遍历所有的有效情况&#xff0c;思考的方向一定是枚举 如果去…...

websocket逆向【python实现websocket拦截】

python实现websocket拦截 前言一、拦截的优缺点优点:缺点:二、实现方法1.环境配置2.代码三、总结前言 开发者工具F12,筛选ws后,websocket的消息是这样显示的,如何获取这里面的消息呢? 以下是本篇文章正文内容 一、拦截的优缺点 主要讲解一下websocket拦截的实现,现在…...

软件测试自动化的成本效益分析

随着软件测试技术的发展&#xff0c;人们已经从最初的手工测试转变为手工和自动化技术相结合的测试方法。目前&#xff0c;人们更多的是关心自动化测试框架、自动化测试工具以及脚本研究等技术方面&#xff0c;而在软件自动化测试方案的效益分析方面涉及较少。 软件测试的目的是…...

【Java】状态修饰符 final static

目录 final 修饰我们的成员方法、成员变量、类 示例代码&#xff1a; final 修饰的局部变量 示例代码&#xff1a; static 示例代码&#xff1a; static 访问特点&#xff1a; 示例代码&#xff1a; static关键字的用途 示例代码&#xff1a; static 修饰常量 示例…...

笔试编程ACM模式JS(V8)、JS(Node)框架、输入输出初始化处理、常用方法、技巧

目录 考试注意事项 先审完题意&#xff0c;再动手 在本地编辑器&#xff08;有提示&#xff09; 简单题515min 通过率0%&#xff0c;有额外log 常见输入处理 str-> num arr&#xff1a;line.split( ).map(val>Number(val)) 初始化数组 new Array(length).fill(v…...

learn掩码张量

目录 1、什么是掩码张量 2、掩码张量的作用 3、代码演示 &#xff08;1&#xff09;、定义一个上三角矩阵&#xff0c;k0或者 k默认为 0 &#xff08;2&#xff09;、k1 &#xff08;3&#xff09;、k-1 4、掩码张量代码实现 &#xff08;1&#xff09;、输出效果 &…...

激活函数介绍

介绍 神经网络当中的激活函数用来提升网络的非线性&#xff0c;以增强网络的表征能力。它有这样几个特点&#xff1a;有界&#xff0c;必须为非常数&#xff0c;单调递增且连续可求导。我们常用的有sigmoid或者tanh&#xff0c;但我们都知道这两个都存在一定的缺点&#xff0c…...

docker方式启动一个java项目-Nginx本地有代码,并配置反向代理

文章目录 案例导入说明1.安装MySQL1.1.准备目录1.2.运行命令1.3.修改配置1.4.重启 2.导入SQL3.导入Demo工程3.1.分页查询商品&#xff08;仔细看代码&#xff0c;很多新的MP编程技巧&#xff09;3.2.新增商品3.3.修改商品3.4.修改库存3.5.删除商品3.6.根据id查询商品3.7.根据id…...

前端和后端是Web开发选哪个好?

前端和后端是Web开发中的两个不同的领域&#xff0c;哪一种更适合学习&#xff1f;前景更广呢&#xff1f; 一、引言 Web前端开发就像装饰房间的小瓦匠&#xff0c;勤勤恳恳&#xff0c;仔仔细细&#xff0c;粉饰墙壁&#xff0c;妆点家具。会 HTML,CSS&#xff0c;懂点 JS。…...

HTTP协议,请求响应

、概述 二、HTTP请求协议 三、HTTP响应协议 四、请求数据 1.简单实体参数 RequestMapping("/simpleParam")public String simpleParam(RequestParam(name "name" ,required false ) String username, Integer age){System.out.println (username "…...

idea配置文件属性提示消息解决方案

在项目文件路径下找到你没有属性提示消息的文件 选中&#xff0c;ok即可 如果遇到ok无法确认的情况&#xff1a; 在下图所示位置填写配置文件名称即可...

EdgeView 4 for Mac:重新定义您的图像查看体验

您是否厌倦了那些功能繁杂、操作复杂的图像查看器&#xff1f;您是否渴望一款简单、快速且高效的工具&#xff0c;以便更轻松地浏览和管理您的图像库&#xff1f;如果答案是肯定的&#xff0c;那么EdgeView 4 for Mac将是您的理想之选&#xff01; EdgeView 4是一款专为Mac用户…...

流程自动化(RPA)的好处有哪些?

流程自动化&#xff08;RPA&#xff09;是一种通过软件机器人实现业务流程自动化的技术。它可以模拟人类在计算机上执行的操作&#xff0c;从而自动化重复性、繁琐的任务&#xff0c;提高工作效率和准确性。流程自动化&#xff08;RPA&#xff09;的好处很多&#xff0c;下面我…...

医学影像系统【简称PACS】源码

PACS(Picture Archiving and Comuniations Systems)即PACS&#xff0c;图像存储与传输系统&#xff0c;是应用于医院中管理医疗设备如CT&#xff0c;MR等产生的医学图像的信息系统。目标是支持在医院内部所有关于图像的活动&#xff0c;集成了医疗设备&#xff0c;图像存储和分…...

大家都在用哪些敏捷开发项目管理软件?

敏捷开发是一种以人为核心、迭代、循序渐进的开发方法。 敏捷开发的特点是高度灵活性和适应性、迭代式开发。 敏捷开发方法强调快速响应变化&#xff0c;因此它具有高度的灵活性和适应性。开发团队可以根据客户需求和市场变化快速调整开发计划和产品功能&#xff0c;以确保产品…...

python机器学习基础教程01-环境搭建

书籍源代码 github上源代码 https://github.com/amueller/introduction_to_ml_with_python 安装anaconda虚拟环境 创建虚拟环境 conda create -p E:\Python\envs\mlstupy35 python3.5 # 激活环境 conda activate E:\Python\envs\mlstupy35 # 创建学习目录 cd G:\Python\ml…...

TinyWebServer学习笔记-Config

为了弄清楚具体的业务逻辑&#xff0c;我们直接从主函数开始看源代码&#xff1a; #include "config.h"int main(int argc, char *argv[]) {//需要修改的数据库信息,登录名,密码,库名string user "root";string passwd "root";string databas…...

数据结构与算法--算法

这里写目录标题 线性表顺序表链表插入删除算法 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 线性表 顺序表 链表 插入删除算法 步骤 1.通过循环到达指定位置的前一个位置 2.新建…...

wordpress文章关联/网盘搜索神器

4个类就可体验HADOOP-RPC的简单、实用。 一&#xff0c;writable-rpc 一&#xff0c;协议 所谓协议&#xff0c;实际是一个接口&#xff0c;用来定义该RPC提供的功能 package rpc.writeable;public interface MyProtocol {void mkdir(String path);String getName(String na…...

系统开发工具有哪些/北京seo招聘

有符号数的表示法&#xff0c;机器数(出现在电脑的二进位数值)有3个特点&#xff0c; 无符号或符号转换成数值来表示&#xff0c;没有 10101这样的资料&#xff0c;而是以010101来表示。 (推荐学习&#xff1a;phpstorm)只表示单纯的整数或小数&#xff0c;小数点的位置预设在一…...

网站设计弹窗/百度怎么推广产品

L1-033 出生年&#xff08;15 分&#xff09; 以上是新浪微博中一奇葩贴&#xff1a;“我出生于1988年&#xff0c;直到25岁才遇到4个数字都不相同的年份。”也就是说&#xff0c;直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求&#xff0c;自动填充“我出生于y…...

免费创造网站/自己怎么制作网站

谢邀。 先提一下&#xff0c;我并没有用IDE写Python的习惯。因为平时主要就写一点数据分析&#xff0c;这些的话我完全可以用VS Code来实现&#xff0c;而PyCharm什么的IDE&#xff0c;毕竟没Editor好用。后来是因为组织有送Visual Studio Enterprise的key&#xff0c;那就恭敬…...

做网站先做前台还是后台/百度指数是怎么计算的

前言 在使用 python 制作网页的过程中&#xff0c;我们往往需要先将站点的目录“虚拟化”。虚拟化其实就是将当前文件下程序的运行环境与整个系统的环境隔离。那么为什么我们要将一个项目虚拟化呢&#xff1f; 1.不进行虚拟化会产生的问题 在平时使用 python 时&#xff0c;有可…...

郑州做网站/谷歌推广技巧

Numpy 修炼之道&#xff08;1&#xff09; —— 什么是 Numpy Numpy 是什么 简单来说&#xff0c;Numpy 是 Python 的一个科学计算包&#xff0c;包含了多维数组以及多维数组的操作。 Numpy 的核心是 ndarray 对象&#xff0c;这个对象封装了同质数据类型的n维数组。起名 ndarr…...