【Linux取经路】进程控制——进程等待

news2024/9/20 20:41:29

在这里插入图片描述

文章目录

  • 一、进程创建
    • 1.1 初识 fork 函数
    • 1.2 fork 函数返回值
    • 1.3 写时拷贝
    • 1.4 fork 的常规用法
    • 1.5 fork 调用失败的原因
    • 1.6 创建一批进程
  • 二、进程终止
    • 2.1 进程退出场景
    • 2.2 strerror函数
    • 2.3 errno全局变量
    • 2.4 程序异常
    • 2.5 进程常见退出方法
    • 2.6 exit 函数
    • 2.7 _exit 函数和 exit 函数的区别
  • 三、进程等待
    • 3.1 进程等待的必要性
    • 3.2 什么是进程等待?
    • 3.3 进程等待具体是怎么做的?
      • 3.3.1 wait方法
      • 3.3.2 waitpid方法
      • 3.3.3 父进程只等待一个进程(阻塞式等待)
      • 3.3.4 父进程等待多个子进程(阻塞式等待)
    • 3.4 获取子进程的退出信息(阻塞式等待)
    • 3.5 wait、waitpid的实现原理
    • 3.6 非阻塞轮询等待
  • 四、结语

一、进程创建

1.1 初识 fork 函数

在 Linux 中,fork 函数用于从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void); // fork 函数声明
返回值:子进程中返回0;父进程中返回子进程的 pid,出错返回-1

一个进程调用 fork 函数后,当控制转移到内核中的 fork 代码后(执行 fork 函数的代码),内核做了如下一些工作:

  • 分配新的内存块和内核数据结构给子进程。

  • 将父进程部分数据结构内容拷贝到子进程中。

  • 添加子进程到系统进程列表当中。

  • fork 返回,开始调度器调度。

小Tips:其实做完前两步,子进程就已经被创建出来了。

在这里插入图片描述
当一个进程调用 fork 之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程。说的再多还是需要通过代码来演示证明。

#include <stdio.h>    
#include <unistd.h>    
    
int main()    
{    
    printf("befor pid:%d\n", getpid());    
    
    fork();    
    
    printf("after pid:%d\n", getpid());                                                                                                                                                                          
    return 0;    
}

在这里插入图片描述
这里打印了三行输出,一行是 befor 两行是 after。所以,fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。注意,fork 之后,谁先执行完全由调度器决定。

1.2 fork 函数返回值

  • 子进程返回0。

  • 父进程中返回子进程的 pid,出错返回-1。

关于 fork 函数的返回值问题,在【Linux取经路】揭秘进程的父与子一文中已做了详细介绍,其中包括“为什么给子进程返回0,给父进程返回 pid”、“fork 函数是如何做到返回两次的”。感兴趣的小伙伴可以点回去看看。

1.3 写时拷贝

通常,父子进程代码共享,父子进程再不写入的时候,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式再生成一份。
在这里插入图片描述
操作系统是如何知道要进行写时拷贝的呢?答案是:父进程在创建子进程的时候,操作系统会把父子进程页表中的数据项从读写权限设置成只读权限,此后父进程和子进程谁要对数据进行写入就一定会触发权限方面的问题,在进行权限审核的时候,操作系统会识别出来,历史上要访问的这个区域是可以被写入的,只不过暂时是只读状态,父子进程不管谁尝试对数据区进行写入的时候都会触发权限问题,但是针对这这种情况操作系统并不做异常处理,而是把数据拷贝一份,谁写的就把页表项进行重新映射,在拷贝完成后,就把只读标签重新设置成可读可写。

操作系统为什么要采用写时拷贝呢?父进程在创建子进程的时候,单纯的从技术角度去考虑,操作系统完全可以让父子进程共享同一份代码,然后把父进程的多有数据全部给子进程拷贝一份,技术上是完全可以实现的,但是操作系统为什么没有这样干?而是采用写时拷贝呢?原因主要有以下几点,首先假设父进程中国有100个数据,子进程只需要对其中的一个进行修改,剩下的99个子进程只读就可以,那如果操作系统把这100个数据全给子进程拷贝了一份,无疑是干了一件吃力不讨好的工作,全部拷贝既浪费了时间又浪费的物理内存,操作系统是绝对不会允许这种情况发生的,因此,对于数据段,操作系统采用的是写时拷贝的策略。

1.4 fork 的常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端的请求,生成子进程来处理请求。

  • 一个进程要执行一个不同的程序。例如子进程从 fork 返回后,调用 exec 函数。

1.5 fork 调用失败的原因

  • 系统中有太多的进程。

  • 实际用户的进程数超过了限制。

1.6 创建一批进程

通过 for 循环创建一批进程。

#include <stdio.h>      
#include <unistd.h>      
#include <stdlib.h>      
    
#define N 5                                                                                            
                      
void func()           
{                      
    int cnt = 10;      
    while(cnt)        
    {                                                                       
        printf("I am chid, pid:%d, ppid:%d\n", getpid(), getppid());      
        cnt--;                                                            
        sleep(1);                                                         
    }                 
    return;           
}                     
                      
int main()            
{                     
    int i = 0;                  
    for(i = 0; i < N; i++)      
    {                           
        pid_t id = fork();                  
        if(id == 0)// 只有子进程会进去      
        {                                 
            func();                                
            exit(0);// 子进程走到这里就退出了      
        }                                        
    }                                            
    sleep(1000);      
    return 0;       
}

在这里插入图片描述
小Tips:父进程执行的速度是很快的,由于父进程的 for 循环里没有 sleep 函数,所以五个子进程几乎是在同一时间被创建出来,创建出来的每一个子进程会去调用 func 函数,每一个子进程执行完 func 函数后会执行 exit 函数退出。父子进程谁先执行完全是由调度器来决定的。

二、进程终止

2.1 进程退出场景

  • 代码运行完毕,结果正确

  • 代码运行完毕,结果不正确

  • 代码异常终止

一般代码运行完毕,结果正确,我们是不会关心代码为什么跑对了。但是当代码运行完毕,结果不正确,我们作为程序员是需要知道为什么结果不正确,因此进程需要将运行结果以及不正确的原因告诉程序员。这就是 main 函数里常写的 return 0 的作用。return 后面跟的数字叫做进程的退出码,表征进程的运行结果是否正确,不同的返回数字表征不同的出错原因,0表示 success。main 函数 return 的这个0,最终会被父进程,即 bash 拿到。可以在 bash 中输出 echo $? 指令查看上一个子进程的退出码。$? 表示命令行当中最近一个进程运行的退出码。

int main()    
{    
    printf("模拟一段逻辑!\n");    
    return 0;                                                                                          
}

在这里插入图片描述
小Tips:对于一个进程,一般而言只有父进程最关心它的运行情况

2.2 strerror函数

上面提到的退出码本质上是数字,它更适合机器去查看,作为程序员我们可能对数字没有那么敏感,即可能不知道该数字表示的是什么意思。因此 strerror 函数的作用就是将一个退出码转换成为一个错误信息描述。可以通过下面这段代码来打印当前系统支持的所有错误码对应的错误信息。

int main()      
{      
    int i = 0;      
    for(; i < 200; i++)      
    {      
        printf("%d, %s\n", i, strerror(i));                                                            
    }                              
    return 0;                      
} 

在这里插入图片描述

2.3 errno全局变量

errno 是 C 语言给我们提供的一个全局变量,它里面保存的是最近一次执行的错误码,何谓最近一次执行?C 语言为我们提供了很多的库函数,在调用这些库函数失败的时候,C 语言就会将 errno 设置成对应的数字,这个数字就表示调用该函数出错的错误码。

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <string.h>    
#include <errno.h>                                                                                   
    
int main()    
{    
    int ret = 0;    
    char* str = (char*)malloc(1000*1000*1000*4);    
    if(str == NULL)    
    {    
        printf("malloc error:%d, %s\n", errno, strerror(errno));    
        ret = errno;    
    }    
    else    
    {    
        printf("malloc success!\n");    
    }    
    return ret;    
}

在这里插入图片描述

2.4 程序异常

代码如果出现了异常,本质上代码可能就没有跑完,因此可能就没有执行 return 语句。所以程序如果出现了异常,那么该程序的退出码是没有意义的。因此对于一个执行结束的进程来说,我们要先看它是否出异常,如果没有异常再去看它的退出码是否正确。对于异常我们也需要知道程序为什么异常,以及发生了什么异常。进程出现异常,本质上是我们的进程收到了对应的信号。像程序中除0,空指针解引用,一般都会引发硬件错误,由我们的操作系统向对应的进程发送信号。Linux 系统的所有信号如下图所示。

在这里插入图片描述

int main()    
{    
    char* pc = NULL;    
    *pc = 'a'; // 解引用空指针,会发生段错误
    return 0;
}

在这里插入图片描述

下面证明该异常是因为程序收到了对应的信号。

在这里插入图片描述

2.5 进程常见退出方法

正常终止(指程序的代码执行完了结束,而不是收到信号结束)。

  • 从 main 函数返回,即 return

  • 调用 exit 函数

  • _exit

异常退出。

  • ctrl+c

  • 信号终止

2.6 exit 函数

#include <unistd.h>
void exit(int status);

在代码中的任何地方调用 exit 函数,都表示调用进程直接退出。退出码就是 exit 函数的参数 status。说这个主要是为了区分 returnexit,return 只有在主函数(main)中出现才表示进程退出,在普通的函数中使用 return 仅表示函数返回,而在函数中使用 exit,也会让进程直接退出。

2.7 _exit 函数和 exit 函数的区别

在这里插入图片描述
上面的现象我们是可以理解的,printf 函数后面没有加 \n,因此要打印的内容先被保存在了缓冲区中,等休眠两秒后,程序执行 exit 退出,程序退出会刷新缓冲区,所以程序运行我们看到的效果是前两秒什么也没打印,在程序退出前才执行了打印。下面我们把 exit 换成 _exit 再看看效果。

在这里插入图片描述
这次程序执行后,等待了两秒直接退出了,并没有将信息打印出来。

结论_exit 是系统调用,exit 是库函数。exit 最后会调用 _exit,但是在调用 _exit 之前,还做了下面几个工作。

  • 执行用户通过 atexit 或 on_exit 定义的清理函数。

  • 关闭所有打开的流,所有的缓冲区数据均被写入。

  • 调用 _exit()。

在这里插入图片描述

小Tips:通过上面的现象我们可以的出一个结论,那就是缓冲区一定不在内核中,而是在用户空间。因为如果在内核中,那调用 _exit 函数的时候,也必然会把缓冲区中的数据进行刷新,如果不刷新,那还维护这个缓冲区干嘛呢?正是因为缓冲区在用户区,_exit 作为系统调用看不到用户区的数据,所以才没办法刷新。

三、进程等待

3.1 进程等待的必要性

  • 在前面的文章中讲过,子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而会造成内存泄露

  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 指令也无能为力,因为谁也没有办法杀死一个已经死去的进程

  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。

  • 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息

总结:僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄露的问题,这是进程等待的必要性。其次通过进程等待,让父进程获得子进程的退出情况,看布置的任务完成的怎么样了,这一点对父进程来说是可选项,即父进程也可以选择不关心,如果要关心了,需要通过进程等待去获取。

3.2 什么是进程等待?

进程等待就是在父进程的代码中,通过系统调用 wait/waitpid,来进行对子进程进行状态检测与回收的功能。

3.3 进程等待具体是怎么做的?

3.3.1 wait方法

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);
  • 返回值:成功,返回被等待进程的 pid,失败返回-1。

  • 参数:输出型参数,获取子进程的退出状态,不关心则可以设置成为 NULL。

3.3.2 waitpid方法

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int* status, int options);
  • 返回值:当正常返回的时候 waitpid 返回等待到的子进程的进程 ID;如果设置了选项 WNOHANG,而调用的过程中没有子进程退出,则返回0;如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在。

  • 参数 pidpid = -1 表示等待任意一个子进程。与 wait 等效;pid > 0 表示等待进程 ID 与 pid 相等的子进程。

  • 参数 statusWIFEXITED(status):查看子进程是否正常退出。若为正常终止子进程返回的状态,则为真;WEXITSTATUS(status):查看进程的退出码。若非零,提取子进程的退出码。

  • 参数 options:0:表示父进程以阻塞的方式等待子进程,即子进程如果处在其它状态,不处在僵尸状态(Z状态),父进程会变成 S 状态,操作系统会把父进程放到子进程 PCB 对象中维护的等待队列中,以阻塞的方式等待子进程变成僵尸状态,当子进程运行结束,操作系统会检测到,把父进程重新唤醒,然后回收子进程;WNOHANG非阻塞轮询等待,若 pid 指定的子进程没有结束,处于其它状态,则 waitpid() 函数返回0,不予等待。若正常结束,则返回该子进程的 ID。

小Tips:wait 和 waitpid 都只能等待该进程的子进程,如果等待了其它的进程那么就会出错。

3.3.3 父进程只等待一个进程(阻塞式等待)

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
    pid_t id = fork();    
    if(id < 0)    
    {    
        perror("fork");    
        return 1;    
    }    
    else if(id == 0)    
    {    
        // child    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);    
            sleep(1);    
        }    
        exit(0);    
    }    
    else    
    {    
        int cnt = 10;    
        // parent    
        while(cnt)    
        {    
            printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);    
            sleep(1);    
        }    
    
        int ret = wait(NULL);    
        if(ret == id)    
        {    
            printf("wait success!\n");    
        }    
        sleep(5);                                                                                                                                               
    }    
    
    return 0;    
}

在这里插入图片描述
结果分析:前五秒父子进程同时运行,紧接着子进程退出变成僵尸状态,五秒钟后父进程对子进程进行了等待,成功将子进程释放掉,最后再五秒钟后父进程也退出,整个程序执行结束。

3.3.4 父进程等待多个子进程(阻塞式等待)

一个 wait 只能等待任意一个子进程,因此父进程如果要等待多个子进程可以通过循环来多次调用 wait 实现等待多个子进程。

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
#define N 5    
// 父进程等待多个子进程    
void RunChild()    
{    
    int cnt = 5;    
    while(cnt--)    
    {    
        printf("I am child, pid:%d, ppid:%d\n", getpid(), getppid());    
        sleep(1);    
    }    
    return;    
}    
int main()    
{    
    for(int i = 0; i < N; i++)    
    {    
        pid_t id = fork();// 创建一批子进程    
        if(id == 0)    
        {    
            // 子进程    
            RunChild();    
            exit(0);    
        }    
        // 父进程    
        printf("Creat process sucess:%d\n", id);    
    }    
    
    sleep(10);    
    
    for(int i = 0; i < N; i++)    
    {    
        pid_t id = wait(NULL);                                                                                
        if(id > 0)    
        {    
            printf("Wait process:%d, success!\n", id);    
        }    
    }    
    
    sleep(5);    
    return 0;    
}

在这里插入图片描述
小Tips:如果子进程不退出,父进程在执行 wait 系统调用的时候也不返回(默认情况),默认叫做阻塞状态。由此可以看出,一个进程不仅可以等待硬件资源,也可以等待软件资源,这里的子进程就是软件。

3.4 获取子进程的退出信息(阻塞式等待)

在 2.1 小结提到过,进程有三种退出场景。正是因为有这三种退出场景,父进程等待希望获得子进程退出的以下信息:子进程代码是否异常;没有异常,结果对嘛?不对是因为什么呢? 子进程这些所有的退出信息都被保存在 status 参数里面。

  • waitwaitpid 都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。

  • 如果传递 NULL,表示不关心子进程的退出状态信息。

  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

  • status 不能简单的当做整形来看待,可以当做位图来看待,具体细节如下图(只需要关注 status 低16比特位)

在这里插入图片描述
小Tips:操作系统没有0号信号,因此,如果低七位是0说明子进程没有收到任何信号。

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {
        // child
        int cnt = 5, a = 10;
        while(cnt)
        {
            printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1);
            a /= 0; // 故意制造一个异常
        }
        exit(11); // 将退出码故意设置成11
    }
    else 
    {
        // parent
        int cnt = 10;
        while(cnt)
        {
            printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1); 
        }

        // 目前为止,进程等待是必须的!
        //int ret = wait(NULL);
        int status = 0;
        int ret = waitpid(id, &status, 0);
        if(ret == id)
        {
        	// 获取子进程退出状态信息的关键代码
            // 0111 1111:0x7F,1111 1111 0000 0000:0xFF00
            printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF); 
        }
        sleep(5);
    }
    return 0;
}

在这里插入图片描述
小Tips:通过运行结果可以看出,子进程收到了8号信号,子进程的退出码是0。代码中子进程的退出码被我们设置成了11,这侧面印证了我们上面讲到的,进程收到信号后被异常终止,此时代码没有执行完毕,所以此时进程的退出码是不可信的。

// 常规的进程等待代码
int status = 0;
int ret = waitpid(id, &status, 0);
if(ret == id)
{
    // 0111 1111:0x7F,1111 1111 0000 0000:0xFF00
    //printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF);
    if(WIFEXITED(status))
    {
        printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));
    }
    else 
    {
        printf("子进程被异常终止!\n");
    }
}

3.5 wait、waitpid的实现原理

一个进程在退出后,父进程回收之前,它的代码和数据都被释放了,但是它的 PCB 对象并没有被释放,因为它收到的信号和退出码信息都保存在 PCB 对象中,wait 和 waitpid 本质上就是操作系统去检查一个进程是否处于僵尸状态(Z状态),如果处于 Z 状态就去它的 PCB 对象中拿到该进程收到的信号和退出码信息,再把这些信息赋值给 status,然后将该进程的状态设置成 X。这个工作只能由操作系统来做,因为 PCB 对象属于内核数据结构对象,不允许用户直接访问。

3.6 非阻塞轮询等待

前面说过,若父进程采用阻塞式等待,如果子进程没有处于僵尸状态,那么此时父进程处于阻塞状态什么也干不了。若父进程采用非阻塞轮询等待,如果子进程没有处于僵尸状态,那么父进程可以继续去干它的事情。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

// 父进程只等待一个子进程(非阻塞轮询等待)
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {
        // child
        int cnt = 5;
        while(cnt)
        {
            printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1);
            //a /= 0;
        }
        exit(11);
    }
    else 
    {
        // parent 
        // 目前为止,进程等待是必须的!
        //int ret = wait(NULL);
        while(1)
        {
            int status = 0;
            int ret = waitpid(id, &status, WNOHANG);
            if(ret > 0)
            {
                // 0111 1111:0x7F,1111 1111 0000 0000:0xFF00
                //printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF);
                if(WIFEXITED(status))
                {
                    printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));
                }
                else 
                {
                    printf("子进程被异常终止!\n");
                }
                break;
            }
            else if(ret == 0)
            {
            	// 父进程的任务可以写在这里
                printf("child process is running...\n");
            }
            else
            {
                printf("等待出错!\n");
            }
            sleep(1);
        }
        sleep(2);
    }

    return 0;
}

在这里插入图片描述
小Tips:在非阻塞轮询等待过程中父进程可以去执行自己的任务,前提是该任务轻量化且可返回,非阻塞轮询等待的核心任务还是回收子进程。子进程创建出来父子进程谁先执行是由调度器说了算,进程等待在一定程度上确保了父进程一定是最后一个退出的,这样可以避免子进程变为僵尸进程,进而导致内存泄露的问题。

四、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

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

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

相关文章

2024年股市走向!温州哪家证券公司股票开户佣金最低呢,最低可以多少?

​ 对于2024年股市的预测很难做出准确的判断&#xff0c;因为股市受到多种因素的影响&#xff0c;包括经济状况、政策变化、国际形势等。然而&#xff0c;我们可以根据当前的一些趋势和因素来对未来的股市做出一些预测。 首先&#xff0c;随着全球经济的逐步恢复&#xff0c…

【从零开始的rust web开发之路 三】orm框架sea-orm入门使用教程

【从零开始的rust web开发之路 三】orm框架sea-orm入门使用教程 文章目录 前言一、引入依赖二、创建数据库连接简单链接连接选项开启日志调试 三、生成实体安装sea-orm-cli创建数据库表使用sea-orm-cli命令生成实体文件代码 四、增删改查实现新增数据主键查找条件查找查找用户名…

【C语言】初阶指针(2)

目录 前言 1. 指针访问数组 1.1 数组名的含义 1.2 使用指针访问数组 2. 一维数组传参的本质 3. 二级指针 4. 指针数组 4.1 指针数组模拟二维数组 结语 前言 在本篇文章中&#xff0c;我们将要一起来探讨指针与数组之间的关系&#xff0c;以及如何理解指针数组及其运用…

Vue.js 中子组件向父组件传值的方法

Vue.js 是一款流行的 JavaScript 前端框架&#xff0c;它提供了一套完整的工具和 API&#xff0c;使得开发者可以更加高效地构建交互式的 Web 应用程序。其中&#xff0c;组件化是 Vue.js 的一个核心概念&#xff0c;通过组件化可以将一个复杂的应用程序拆分成多个独立的部分&a…

通过与chatGPT交流实现零样本事件抽取

1、写作动机&#xff1a; 近来的大规模语言模型&#xff08;例如Chat GPT&#xff09;在零样本设置下取得了很好的表现&#xff0c;这启发作者探索基于提示的方法来解决零样本IE任务。 2、主要贡献&#xff1a; 提出了基于chatgpt的多阶段的信息抽取方法&#xff1a;在第一阶…

VSCode 插件集

文章目录 翻译(英汉词典)Auto Close TagAuto Rename TagBetter CommentsBracket Pair Colorization TogglerChinese (Simplified) (简体中文)colorizeHighlight Matching TagImage previewJAR ViewerLive ServerMarkdown Preview EnhancedMaterial Icon ThemeMaterial Themeope…

uniapp 使用canvas 画海报,有手粘贴即可用(拆成组件了,看后面)

1.直接使用 html部分 <view click"doposter">下载海报</view> <canvas canvas-id"myCanvas" type2d style"width: 370px; height: 550px;opcity:0;position: fixed;z-index:-1;" id"myCanvas" />js 部分 drawBac…

【Vue】2-11、组件的生命周期

一、生命周期 & 声明周期函数 生命周期&#xff08;Life Cycle&#xff09;是值一个组件从 创建 -> 运行 -> 销毁 的整个阶段&#xff0c;强调的是一个时间段。 生命周期函数是由 Vue 框架提供的内置函数&#xff0c;会伴随着组件的生命周期&#xff0c;自动按次序…

【数据分享】1929-2023年全球站点的逐日最高气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

机器学习数学基础

机器学习基础 1、标量、向量、矩阵、张量2、概率函数、概率分布、概率密度、分布函数3、向量的线性相关性4、最大似然估计5、正态分布(高斯分布)6、向量的外积(叉积)7、向量的内积(点积)8、超平面(H)1、标量、向量、矩阵、张量 标量、向量、矩阵和张量是线性代数中不同…

周期承压下的徐工机械:收入持续负增长,大肆并购风险犹存

撰稿|行星 来源|贝多财经 工程机械行业的发展程度是衡量工业化水平的关键指标&#xff0c;亦是一直以来备受国家与市场关注的高成长板块。 在探索新发展增量的大军中&#xff0c;徐工机械&#xff08;SZ:000425&#xff09;活跃工程机械市场&#xff0c;寻求着利润与品质的最…

springboot140体育馆使用预约平台的设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

草图导入3d之后渲染模型发光怎么回事?---模大狮模型网

在草图大师中&#xff0c;当导入3D模型之后发现模型发光通常是由于模型的材质属性或灯光设置所导致的。以下是一些可能的原因和解决方法&#xff1a; 材质属性设置&#xff1a;某些3D模型文件可能包含了发光材质属性&#xff0c;导致模型在草图大师中显示为发光状态。您可以尝试…

第四次项目(配置dns服务的正反向解析)

目录 实验要求 实验步骤 一、基础配置 1.1、服务端配置静态IP 1.2、客户端配置静态IP 二、配置dns服务的正向解析 2.1、服务端编辑主配置文件named.conf 2.2、服务端编辑主配置文件named.rfc1912.zones 2.3&#xff0c;服务端编辑数据配置文件&#xff0c;使用拷贝…

opencvb 十七 使用cmake配置opencv c++项目

1、cmake简介 1.1 cmake是什么 CMake是一个开源、跨平台的编译&#xff08;Build&#xff09;工具&#xff0c;是用来构建、测试和打包软件的。它能够用简单的语句来描述所有平台的编译过程。它能够输出各种各样的makefile或者project文件&#xff0c;能测试编译器所支持的C特…

【ASP.NET Core 基础知识】--Web API--创建和配置Web API(一)

一、简介 Web API&#xff08;Web Application Programming Interface&#xff09;的重要性在于其在现代软件开发中扮演着关键的角色。以下是一些关于Web API重要性的方面&#xff1a; 跨平台交互&#xff1a; Web API允许不同平台、不同技术栈的应用程序进行通信。无论是Web…

C#网络爬虫之TianyaCrawler实战经验分享

互联网时代的到来带来了大量的数据&#xff0c;而网络爬虫技术成为了获取这些数据的重要途径之一。如果你是一名C#开发者&#xff0c;那么你可能会对TianyaCrawler这个强大的网络爬虫框架感兴趣。本文将带你深入了解TianyaCrawler&#xff0c;分享它的技术概况、使用场景&#…

为什么光纤目前取代不了网线?

早上好&#xff0c;我的网工朋友。 在布线行业中&#xff0c;光纤与铜缆之间的较量已持续了十多年。 现如今随着云计算、5G等新型业务的不断涌现&#xff0c;数据中心规模不断的扩大&#xff0c;其架构与布线也越来越复杂。 但光纤的轻量化及逐渐降低的成本&#xff0c;使得…

Ubuntu Linux 下安装和卸载cmake 3.28.2版本

一、安装cmake 1.首先&#xff0c;先从cmake官网下载cmake-3.28.2-linux-x86_64.tar.gz 2.用FinalShell 等文件上传工具&#xff0c;将这个压缩包上传到 虚拟机的某个路径去&#xff08;自选&#xff09; 3. cd /usr/local/bin/&#xff0c;然后创建cmake文件夹&#xff0c;…

2024.2.1每日一题

LeetCode 今天看到一个评论挺有意思的&#xff0c;非常符合我现在的状况 简单题 – 稍加思索&#xff0c;嘴角上扬 中等题 – 认真对待&#xff0c;眉头一皱 困难题 – 绞尽脑汁&#xff0c;Ctrl cv 数字游戏 LCP 24. 数字游戏 - 力扣&#xff08;LeetCode&#xff09; 题目…