Linux---进程信号

news2024/12/23 10:33:24

一、信号的概念

 信号是一种向目标进程发送通知消息的机制

信号的特性(可以结合红绿灯、防空警报等生活样例来理解)

1、在信号没有出现之前,我们就已经知道如何去处理信号,即我们认识信号

2、信号是异步产生的,即我们不知道它具体何时产生

3、当信号产生时,我们可以对它暂时忽略不做处理(比如我们外卖到了,但是你正在和朋友开黑,就会将外卖暂时搁置)

4、由于我们有时不会立即去执行信号,所以我们需要能储存信号

信号列表如下

一些补充的知识

二、信号的产生

 1、通过键盘进行信号的产生

解释如下

该系统调用接口可以自定义捕捉信号的行为,将signum信号的默认行为改为handler

//process.cc
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void handler(int signo)
{
    std::cout<<"发送了一个2号信号"<<std::endl;
    exit(1);
}

int main()
{
    signal(2,handler);
    std::cout << "pid: " << getpid() << std::endl;
    while(1)
    {
        std::cout << "running" << std::endl;
        sleep(1);
    }
    return 0;
}

对信号的进一步理解

 man 7 singal  查看信号的默认行为

既然我们能通过signal系统调用将信号的默认方法改变,那么我们能否将所有能杀死进程的信号改掉,使得进程无法被终止呢???

 很显然是不行的,有些信号是无法被自定义捕捉,比如9号信号,保证了OS的安全

2、通过系统调用进行信号的产生

功能:向pid进程发送sig信号(kill命令就是调用的该系统调用)

举个例子(写一个自己的kill命令)

//test.c
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("%d\n",getpid());
    while(1)
    {
        printf("running\n");
        sleep(1);
    }
    return 0;
}

//mykill.cc
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        std::cout<<"\nUsage:" << argv[0] <<" -signnumber processid" << std::endl;
        return 0;
    }
    int signnumber = std::stoi(argv[1]+1);
    int pid = std::stoi(argv[2]);
    kill(pid, signnumber);
    return 0;
}

功能:向本进程发送sig信号

举个例子

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

void handler(int signo)
{
    std::cout<<"发送了一个" << signo <<"号信号"<<std::endl;
}

int main()
{
    signal(2,handler);
    while(1)
    {
        raise(2);
        sleep(1);
    }
    return 0;
}

功能:向本进程发送6号信号,且进程一定会终止

举个例子

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

void handler(int signo)
{
    std::cout<<"发送了一个" << signo <<"号信号"<<std::endl;
}

int main()
{
    std::cout << getpid() << std::endl;
    signal(6,handler);
    // abort();
    while(1)
    {
        std::cout << "running" << std::endl;
        sleep(1);
    }
    return 0;
}

3、通过硬件异常进行信号的产生

原理如下

那么除0异常是发送的哪个信号呢?是8号信号,验证如下

根据上图:发送的确实是8号信号,但现在还有一个问题,进程出异常,OS发送8号信号,可以理解,但是为什么它一直在打印呢,明明我的代码没有循环啊?

因为,原本的8号信号被自定义捕捉成了打印语句,导致进程无法退出,所以进程依旧会在等待队列中等待CPU调度,所以各种寄存器中存放的该进程相关的数据(即进程上下文)都会被保留,包括状态寄存器,而一旦该进程被调度,那么OS就又会检测到硬件错误,向进程发送8信号,如此反复,故有上面的现象发生。

(一旦进程退出,它的相关数据就会被丢弃,因为我们不在需要调度该进程了)

OS杀死进程,就是处理问题的方式之一

程序运行出现异常,如何做取决于用户,但一般都是要让进程退出的(注意:异常的处理,很多时候只是打印错误)

顺便说一下,*nullptr发送的信号是11号信号,本质是页表中没有该地址的物理地址的映射关系,引发的硬件错误

4、通过软件条件进行信号的产生

这个其实在之前的博客中就讲过一些示例,比如管道中只要读端关闭,写端还在写,OS就会向写端发送SIGPIPE,终止写端,它本质是因为OS的内核数据中发现该管道只被一个进程打开,所以发信号终止写端,不是硬件异常,而是软件条件产生的信号。又比如调试程序用的gdb向进程发送的SIGSTOP和SIGCONT都是软件条件产生的信号。

这里再介绍一个alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送SIGALRM信号(14号信号),该信号的默认处理动作是终止当前进程

 演示如下

上面两段代码都是设置了1秒的闹钟,确实时间到了,进程就终止了,但是两段代码cnt++的运行次数却是天差地别,唯一的区别就是有没有与外设进行IO交互,也证明了IO的效率很低

 

多次使用alarm函数,就是刷新闹钟,并返回之前闹钟剩余的时间,注意:alarm(0)不是设置0秒的闹钟,这个相当于清空之前的闹钟并返回之前闹钟剩余的时间

了解(扩展)---帮助理解alarm

这里讲讲alarm是如何实现的,首先每个进程都能设置闹钟,也就是说OS中可以存在多个闹钟,所以需要管理,即先描述在组织,所以我们要设计一个闹钟结构体,里面包含闹钟所属进程id,时间(用时间戳记录)等等(根据需求往里面加属性)。然后就是如何组织,即选择什么样的数据结构进行管理,这里可以选择用小堆,按照时间大小存放,如果时间堆顶闹钟的时间没到,说明所有的都没到,不做处理,如果时间到了,就拿出来处理。(当然这不一定是Linux中alarm的实现,这里只是提供思想,具体如何实现得根据需求)

core dump(核心转储) 

介绍:

分析core dump是Linux应用程序调试的一种有效方式,core dump又称为“核心转储”,是该进程实际使用的物理内存的“快照”。分析core dump文件可以获取应用程序崩溃时的现场信息,如程序运行时的CPU寄存器值、堆栈指针、栈数据、函数调用栈等信息。

Core dump是Linux基于信号实现的。Linux中信号是一种异步事件处理机制,每种信号都对应有默认的异常处理操作,默认操作包括忽略该信号(Ignore)、暂停进程(Stop)、终止进程(Terminate)、终止并产生core dump(Core)等

这里也简单说一下,为什么有的信号终止进程需要核心转储,而有的不需要?

我们可以看一下那些不需要核心转储的终止信号,如SIGKILL,它其实并不是进程出现异常而将进程杀死,更类似于用户强制终止进程,也就是说进程本身并没有问题(或者出错原因很明显),所以我们不需要core dump再去分析异常,但是像SIGSEGV信号,即段错误信号,我们只能知道是内存出现问题,但是具体是数组越界还是其他什么问题引发的我们并不清楚,所以我们需要core dump帮助我们去分析。

但其实通过上面我们的示例代码和结果截图,我们会发现,Term/Core的功能好像都一样,只是终止进程,并没有产生core dump文件,这是什么原因呢?

因为核心转储的文件太大了,我们用的是服务器,默认将core dump大小设置为0,即不生成核心转储,防止服务器被写满(虚拟机应该是开启的)当然可以通过指令打开,如下

注意:核心转储只能在对应的shell中生成,即哪个shell设置了core dump的大小,哪个shell跑程序收到异常才会生核心转储文件

上面的短短5行代码,就需要生成55万字节的文件,如果代码在多一点,文件只会更大,所以为了保证服务的安全,系统默认将core dump文件大小设置为0

那么核心转储的文件有什么用呢?

三、信号存储

信号的相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)---即在信号位图中
  • 进程可以选择阻塞 (Block )某个信号---未决之后,暂时不递达

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

如何在OS中体现上面说的三个概念---递达、未决、阻塞??

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。不讨论实时信号

四、信号阻塞

sigset_t

未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

信号集操作函数

int sigemptyset(sigset_t *set);将所有信号的对应bit清零,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);将所有信号的对应bit置1,表示该信号集的有效信号包括系统支持的所有信号
int sigaddset (sigset_t *set, int signo);在该信号集中添加某种有效信号
int sigdelset(sigset_t *set, int signo);在该信号集中删除某种有效信号
int sigismember(const sigset_t *set, int signo);用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1

注意:在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态

 sigprocmask

功能介绍:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值

SIG_BLOCKset包含我们希望添加到当前信号屏蔽字的信号,相当于mask = mask|set
SIG_UNBLOCKset包含我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask&~set
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=set

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达

演示如下

当然可能有人会说,既然能屏蔽信号,我们能不能将所有的信号全部屏蔽???

当然不行,跟有些信号无法被自定义捕捉是一个道理,如9号信号,这里就不验证了

 sigpending

读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1

演示从阻塞到递达的过程,如下

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

void handler(int signo)
{
    std::cout << "接收到" << signo << "信号" << std::endl;
}

void Printf_Pending(const sigset_t &pending)
{
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&pending, i))
            std::cout << 1;
        else
            std::cout << 0;
    }
    std::cout << "\n";
}

int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    signal(2, handler);
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, 2);
    sigprocmask(SIG_BLOCK, &set, nullptr); // 屏蔽2号信号
    std::cout << "屏蔽了2号信号" << std::endl;

    int cnt = 0;
    while (1)
    {
        sigset_t pending;
        sigpending(&pending);
        Printf_Pending(pending);
        sleep(1);
        cnt++;
        if (cnt == 10)
        {
            std::cout << "解除对2号信号的阻塞" << std::endl;
            sigprocmask(SIG_UNBLOCK, &set, nullptr);
        }
    }
    return 0;
}

这里有一个问题:pending位图的置0操作和信号的递达,谁先发生???

我们可以在handler方法中打印pending位图,如果已经为0,则置0操作先发生,反之,相反

显然,我们是先将pending位图的对应信号比特位置0,在执行信号的递达操作

五、信号的捕捉

信号在什么时候被处理?

--- 进程从内核态返回用户态的时候,进行信号的检测和递达

  • 用户态是一种受控的状态,能够访问的资源是有限的
  • 内核态是一种OS的共工作状态,能访问大部分系统资源

系统调用的背后,就包含了身份的变化!!!

补充一些进程地址空间的内容

内核空间对应的页表和OS资源只需要一份,因为所有的进程都需要,就像动态库资源我们只要加载一份就行,需要的就去映射。所以CPU在任何时间都能访问OS

可能有人会觉得:如果我的程序中只是在死循环的打印语句,没有进行系统调用,那么信号是不是就被处理了?

当然不是,进程是要被OS调度切换的,而当进程被放在CPU上执行时,本质就已经完成了从OS(内核)到用户的转换,所以信号有无数次机会被检测处理

Sigaction

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

  • 功能:可以读取和修改与指定信号相关联的处理动作。调用成功返回0,出错返回-1
  • 参数:signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

(只关注被框出来的两个成员)

将sa_handler赋值为常数SIG_IGN表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号。和signal函数的第二个参数类似

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,这里我们不关心所以把sa_flags设为0,sa_sigaction是实时信号的处理函数

演示如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void Printf_Pending(const sigset_t &pending)
{
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&pending, i))
            std::cout << 1;
        else
            std::cout << 0;
    }
    std::cout << "\n";
}

void handler(int signo)
{
    std::cout << "接收到" << signo << "信号" << std::endl;
    while (1)
    {
        sigset_t pending;
        sigpending(&pending);
        Printf_Pending(pending);
        sleep(1);
    }
}

int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    struct sigaction act, oact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaction(2, &act, &oact);

    while (1)
        sleep(1);
    return 0;
}

显然,sigaction函数不仅将自定义捕捉的2号信号在运行时自行屏蔽,而且可以通过sa_mask同时将其他的信号也屏蔽,注意这些屏蔽会在2号信号处理结束返回后解除

并且返回后,它还是会去检测是否有信号需要被递达,至于信号被处理的顺序和信号优先级有关(这里就不演示了)

 六、信号的补充问题(了解)

可重入函数

即可以重复进入的函数,什么意思?举个简单的例子,我们学过用fork创建子进程,当父进程和子进程同时执行printf语句时,就有可能在屏幕上出现交替打印的情况,导致输出的数据不符合预期,这就说明printf语句是不可重入函数。反之,如果多个执行流可以同时进入一个函数,且不发生错误,就是可重入函数。

注意:可重入和不可重入是函数的一个特征,并不能作为评判函数好坏的标准。一般来说,使用公共资源的函数都是不可重入函数。

 volatile---保持内存的可见性

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int flag = 0;
void handler(int sigo)
{
    flag = 1;
    std::cout << "flag changed to :" << flag << std::endl;
}

int main()
{
    signal(2, handler);
    std::cout << "flag:" << flag << std::endl;
    while (!flag)
        ;
    std::cout << "flag:" << flag << std::endl;
    return 0;
}

如果是正常编译程序,不会有问题,一旦优化(相当于release版)就会出bug为什么?

因为flag变量被优化后直接放到了CPU的寄存器中,在信号处理时,我们改变的flag是内存中的flag,并不会改变寄存器中的flag,所以进程无法结束

我们可以给flag加volatile关键字,保证内存的可见性,即保证该变量一直从内存中读取得到

SIGCHLD信号(17号信号)

子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可

1、验证子进程终止是否会向父进程发送SIGCHLD信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>

void handler(int signo)
{
    std::cout << "get " << signo << " sign" << std::endl;
}

int main()
{
    signal(SIGCHLD, handler);
    pid_t id = fork();
    if(id==0)
    {
        std::cout<<"child running"<<std::endl;
        sleep(5);
        exit(1);
    }

    wait(nullptr);
    return 0;
}

2、在handler函数中回收子进程

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>

void handler(int signo)
{
    std::cout << "get " << signo << " sign" << std::endl;
    wait(nullptr);
}

int main()
{
    signal(SIGCHLD, handler);
    std::cout << "father pid: " << getpid() << std::endl;

    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "child pid: " << getpid() << std::endl;
        std::cout << "child running" << std::endl;
        sleep(5);
        exit(1);
    }

    return 0;
}

上面的是针对只有一个子进程的情况,但是如果有多个进程呢?

我们知道如果多个子进程同时退出,发送17号信号,父进程的pending表只会记录一次17号信号,也就只会执行一次wait函数,所以有的子进程就会处于僵尸状态,那我们该如何做?

有人可能觉得用循环就行,如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>

void handler(int signo)
{
    std::cout << "get " << signo << " sign" << std::endl;
    pid_t id;
    while ((id = wait(nullptr)))
    {
        std::cout << "child pid: " << id << " exit" << std::endl;
        if (id < 0)
            break;
    }
}

int main()
{
    signal(SIGCHLD, handler);
    for (int i = 0; i < 5; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            std::cout << "child is running" << std::endl;
            sleep(5);
            exit(1);
        }
    }

    while(1) sleep(1);
    return 0;
}

事实证明也确实行,但是这是所有子进程都结束的情况,如果有一部分子进程终止,另一部分子进程一直在运行,那么我们就无法跳出循环,因为wait是阻塞等待,它可以去查看是否有进程还没终止,这样就会一直在阻塞。

所以用非阻塞等待才是最恰当的,如下

void handler(int signo)
{
    std::cout << "get " << signo << " sign" << std::endl;
    pid_t id;
    while ((id = waitpid(-1, nullptr, WNOHANG)) > 0)
    {
        std::cout << "child pid: " << id << " exit" << std::endl;
    }
}

当然,如果不关心子进程的返回信息,也可以直接忽略该信号,子进程会自动被清理。如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
    signal(SIGCHLD, SIG_IGN);
    for (int i = 0; i < 5; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            std::cout << "child is running" << std::endl;
            sleep(5);
            exit(1);
        }
    }
    while(1) sleep(1);
    return 0;
}

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

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

相关文章

使用el-form之表单校验自动定位到报错位置问题,,提升用户体验

需求描述 由于需要填写的表单项太多&#xff0c;提交的时候校验不通过&#xff0c; 如果没填写的表单项在最上面&#xff0c;用户看不到不知道发生了啥&#xff0c; 所以需要将页面滚动定位到第一个报错的表单项位置&#xff0c;提升用户体验实现步骤 1. 给form表单添加ref …

LangChain---大型语言模型(LLM)的标准接口和编程框架

1.背景说明 公司在新的一年规划中突然提出要搞生成式AI(GenAI)的相关东西&#xff0c;在公司分享的参考资料中了解到了一些相关的信息&#xff0c;之所以想到使用LangChain&#xff0c;是因为在应用中遇到了瓶颈问题&#xff0c;除了已经了解和研究过的OpenAI的ChatGpt&#xf…

Python实现EMV工具判断信号:股票技术分析的工具系列(2)

Python实现EMV工具判断信号&#xff1a;股票技术分析的工具系列&#xff08;2&#xff09; 介绍算法解释&#xff1a;优势&#xff1a;劣势&#xff1a; 代码rolling函数介绍核心代码计算 EMV 完整代码 介绍 先看看官方介绍&#xff1a; EMV(简易波动指标&#xff09; 用法 1.…

MySQL 多表查询 连接查询 内连接

介绍 内连接查询是两张表中交集的部分 连接模式 隐式内连接 SELECT 字段列表 FROM 表1,表2 WHERE 条件显式内连接 SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件案例 有两张表一个表为学生表&#xff0c;另一个表为班级表&#xff0c;现在需要查询学生时候在查…

【Excel PDF 系列】EasyExcel + iText 库实现 Excel 转换 PDF

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言转换前后效果引入 pom 配置代码实现定义 ExcelDataVo 对象主方法EasyExcel 监听器 前言 最近遇到生成 …

【Java程序员面试专栏 算法思维】六 高频面试算法题:动态规划

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,本篇主要聊聊回溯算法,主要就是排列组合问题,所以放到一篇Blog中集中练习 题目关键字解题思路时间空间零钱兑换动态规划+双重循环dp[i]表示兑换金额为i元的最少…

基于springboot+vue的中医慢性病食疗系统

后端语言&#xff1a;java 框架&#xff1a;springboot/ssm 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 前端技术&#xff1a;vue.jsElementUi 开发工具 idea/eclipse/都可以为设计一个安全便捷&#xff0c;并且使用户更好获取中医药膳慢性病食疗平台&#xf…

Mamba与MoE架构强强联合,Mamba-MoE高效提升LLM计算效率和可扩展性

论文题目&#xff1a; MoE-Mamba: Efficient Selective State Space Models with Mixture of Experts 论文链接&#xff1a; https://arxiv.org/abs/2401.04081 代码仓库&#xff1a; GitHub - llm-random/llm-random 作为大型语言模型&#xff08;LLM&#xff09;基础架构的后…

腾讯云优惠代金券领取的3个渠道入口,不看后悔!

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

华为数通方向HCIP-DataCom H12-821题库(多选题:01-20)

第01题 如图BGP下有如下配置,下面哪些描述是错误的? [HUAWEI-bgp] timer keepalive 30 hold 90 [HUAWEI-bgp] peer 1.1.1.2 timer keepalive 10 hold 30A、Timer 取最小值,所以最终结果是 peer1.1.1.2 的 timer值取 keepalive 10 hold 30 B、Peer 配置优先,所以最终结果是…

【Maven】Maven 基础教程(三):build、profile

《Maven 基础教程》系列&#xff0c;包含以下 3 篇文章&#xff1a; Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置Maven 基础教程&#xff08;二&#xff09;&#xff1a;Maven 的使用Maven 基础教程&#xff08;三&#xff09;&#xff1a;b…

开源视频转码器HandBrake

什么是 HandBrake &#xff1f; HandBrake 是一款适用于 Linux、Mac 和 Windows的开源视频转码器。HandBrake 可以处理大多数常见的视频文件和格式&#xff0c;包括消费者和专业摄像机创建的文件、手机和平板电脑等移动设备的文件、游戏和计算机屏幕录制的文件&#xff0c;以及…

【数据结构】实现栈

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解栈&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一 .栈的概念及结构二 .栈的实现栈的结构体初始化销毁栈顶插入栈顶删除显示栈顶元素是否为空栈的大…

Android 签名机制

V1是内部文件单个签 但是增加apk文件目录下面随意增加文件并不会有影响,它只关心meta-info文件 mf汇总清单的各个文件sha256 V2 整个APK文件,按文件进行hash 那么便不能随便在这里面增加文件了,增加了签名分块&#xff08;不然签名信息存哪里&#xff09;这里涉及一个文件概念 …

记录一次架构优化处理性能从3千->3万

0.背景 优化Kafka消费入Es&#xff0c;适配600台设备上报数据&#xff0c;吞吐量到达2万每秒 1.环境配置 2.压测工具 3.未优化之前的消费逻辑 4.优化之后的消费流程 5.多线程多ESclient 6.修改ES配置&#xff0c;增加kafka分区&#xff0c;增加线程&#xff0c;提升吞吐量 7.…

DiskMirror-spring-boot-starter 技术|

DiskMirror-spring-boot-starter 技术 diskMirror 实现了 SpringBoot 的 starter 能够集成到 SpringBoot 中。 DiskMirror 的 starter&#xff0c;通过引入此类&#xff0c;可以直接实现 diskMirror 在 SpringBoot 中的自动配置&#xff0c;接下来我们将使用案例逐步的演示 d…

【多线程】CAS详解

目录 &#x1f334;什么是 CAS&#x1f338;CAS 伪代码 &#x1f38d;CAS 是怎么实现的&#x1f340;CAS 有哪些应⽤&#x1f338;实现原子类&#x1f338;实现自旋锁 &#x1f333;CAS 的 ABA 问题&#x1f338;**什么是 ABA 问题**&#xff1f;&#x1f338;ABA 问题引来的 B…

挑战30天学完Python:Day24 统计分析

&#x1f389; 本系列为Python基础学习&#xff0c;原稿来源于 30-Days-Of-Python 英文项目&#xff0c;大奇主要是对其本地化翻译、逐条验证和补充&#xff0c;想通过30天完成正儿八经的系统化实践。此系列适合零基础同学&#xff0c;或仅了解Python一点知识&#xff0c;但又没…

Docker之数据卷

文章目录 一、什么是数据卷二、自定义镜像 一、什么是数据卷 1.1Docker 数据管理 在生产环境中使用 Docker &#xff0c;往往需要对数据进行持久化&#xff0c;或者需要在多个容器之间进行 数据共享&#xff0c;这必然涉及容器的数据管理操作 1.2操作 将宿主机的目录与容器的目…

【解决(几乎)任何机器学习问题】:交叉验证

在上⼀章中&#xff0c;我们没有建⽴任何模型。原因很简单&#xff0c;在创建任何⼀种机器学习模型之前&#xff0c;我们必须知道什么是交叉检验&#xff0c;以及如何根据数据集选择最佳交叉检验数据集。 那么&#xff0c;什么是 交叉检验 &#xff0c;我们为什么要关注它&…