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

在这里插入图片描述

文章目录

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

四、结语

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql8安装基础操作(一)

一、下载mysql8.0 1.查看系统glibc版本 这里可以看到glibc版本为2.17&#xff0c;所以下载mysql8.0的版本时候尽量和glibc版本对应 [rootnode2 ~]# rpm -qa |grep -w glibc glibc-2.17-222.el7.x86_64 glibc-devel-2.17-222.el7.x86_64 glibc-common-2.17-222.el7.x86_64 gl…

【Tomcat与网络9】提高Tomcat启动速度的八大措施

本文我们来看一下如何对Tomcat进行调优&#xff0c;我们对于Tomcat的调优主要集中在三个方面&#xff1a;提高启动速度、提高系统稳定性和提高并发能力&#xff0c;后两者很多时候是相辅相成的&#xff0c;我们放在一起看。 Tomcat现在一般都嵌入在SpringBoot里&#xff0c;因…

基于ecal的foxglove studio可视化工具的使用

ecal通讯在自动驾驶和机器人中的应用越来越多,在调试测试过程中,可以使用ecal monitor,ecal recoder和ecal player等工具,对ecal 消息进行监测录制回播。但是,有时候需要对消息进行可视化查看,比如雷达点云信息,相机图像等,可以使用foxglove studio可视化工具。 Foxg…

高性能跨平台网络通信框架 HP-Socket v6.0.1

项目主页 : http://www.oschina.net/p/hp-socket开发文档 : https://www.docin.com/p-4592706661.html下载地址 : https://github.com/ldcsaa/HP-SocketQQ Group: 44636872, 663903943 v6.0.1 更新 一、主要更新 优化Linux通信组件多路复用处理架构&#xff0c;避免“惊群”问…

Android 高德地图切换图层

一、默认样式 Android 地图 SDK 提供了几种预置的地图图层&#xff0c;包括卫星图、白昼地图&#xff08;即最常见的黄白色地图&#xff09;、夜景地图、导航地图、路况图层。 findViewById<TextView>(R.id.normal).setOnClickListener {updateSelectedStatus(TYPE_NORMA…

基于SpringBoot+Vue实现的物流快递仓库管理系统

基于SpringBootVue实现的物流快递仓库管理系统 文章目录 基于SpringBootVue实现的物流快递仓库管理系统系统介绍技术选型成果展示账号地址及其他说明源码获取 系统介绍 系统演示 关注视频号【全栈小白】&#xff0c;观看演示视频 基于SpringBootVue实现的物流快递仓库管理系…

【PyQt】02-基本UI

文章目录 前言一、首先了解什么是GUI&#xff1f;二、初学程序1.界面展示代码运行结果 2.控件2.1按钮展示代码运行结果 2.2 纯文本和输入框代码运行结果 3、重新设置大小 -resize4、移动窗口-move()5、设置界面在电脑中央5.1 代码运行结果 6、设置窗口图标代码运行结果 7、布局…

上岸秘籍来啦!TOGAF认证考试全攻略

上岸秘籍来啦&#xff01;手把手教你如何顺利通过TOGAF认证考试&#xff01; &#x1f31f;考试内容 TOGAF 9.2认证分为两个级别&#xff1a; ✅ TOGAF基础级&#xff1a;掌握标准术语、结构和基本概念&#xff0c;理解企业架构和核心标准。 ✅ TOGAF鉴定级&#xff1a;深入分析…

Skywalking trace UI页面中字段信息详解,包括端点、跨度类型和Peer

刚上手Skywalking的同学可能对 trace UI 页面信息中的字段信息不是很了解&#xff0c;在这里就给大家一一讲解&#xff0c;重点关注端点、跨度类型和Peer 服务 :服务的名称 实例&#xff1a;服务对应的实例 端点&#xff1a;端点(Endpoint) 对于特定服务所接收的请求路径, 如…

2024技术发展洞察与趋势学习总结

2023技术发展洞察 2024技术发展趋势

算法--数论

这里写目录标题 质数&#xff08;素数&#xff09;定义判断是否为质数暴力写法&#xff0c;试除法基本思想具体写法 优化基本思想&#xff08;时间复杂度根号n&#xff09;具体写法 分解质因数分析题意暴力写法基本思想具体代码 优化基本思想&#xff08;时间复杂度小于等于根号…

【GEE】基于GEE可视化和下载Landsat8 L2A数据(镶嵌、裁剪)

之前发过一篇使用GEE下载Landsat8的文章&#xff0c;然后有很多小伙伴私信我各种问题&#xff0c;如L1C、L2数据代码怎么修改&#xff0c;如何镶嵌&#xff0c;如何去云、 如何裁剪等一系列问题。正好快过年了&#xff0c;手头的事也没有多少了&#xff0c;所以这两天整理了一下…

解决PS图片打印不清楚(锐化)

解决PS图片打印不清楚&#xff08;锐化&#xff09; 操作如下&#xff1a;

Antd中使用Select框 在点出弹出下拉列表后 鼠标移到外面滚动会导致下拉框位置偏移

今天使用Select框的时候 由于页面可以滚动 出现了 在点出弹出下拉列表后 鼠标移到外面滚动会导致下拉框位置偏移的bug 如图 上下滚动外部页面会导致下拉框偏移 解决方案&#xff1a; Antd 官方文档 文档中Select框有一个元素 getPopupContainer 并且有一行小字提示 注意…

使用goland IDE编写go windows ui

最近突发奇想&#xff0c;想实现一款工作节奏的提示安排小闹钟。那首先解决的就是UI。本人擅长go语言。那go在windows ui的探索肯定有人做过了吧。一查还真有&#xff0c;通过知乎&#xff0c;csdn等查到目前支持最好的就是walk库了。那走起试试。 一、拷贝go代码 将官网例子…

miniReact<一>

一、工程化配置 1.1 目录结构 1.1.1 Multi-repo VS Mono-repo Multi-repo 每个库有自己独立的仓库&#xff0c;逻辑清晰&#xff0c;协同管理复杂 Mono-repo 很方便管理不同独立的库的生命周期&#xff0c;会有更高的操作复杂度 项目有很多包&#xff0c;同时管理多个不同的…

Java技术栈 —— Spring MVC 与 Spring Boot

参考文章或视频链接[1] Spring vs. Spring Boot vs. Spring MVC[2] Key Differences Between Spring vs Spring Boot vs Spring MVC

如何给图片压缩大小?3种方法任你选择!

如何给图片压缩大小&#xff1f;在日常生活中&#xff0c;将图片压缩可以带来很多便利。首先&#xff0c;对于需要在网络上分享或传输的图片&#xff0c;压缩可以大大减少文件大小&#xff0c;加快上传和下载速度。其次&#xff0c;还可以帮助他们节省空间&#xff0c;存放更多…

Flex布局,几行代码就可以实现瀑布流布局,代码简单,定制化强。

原理很简单&#xff0c;计算图片的宽高&#xff0c;再计算每列的使用高度&#xff0c;然后再将当前图片放置在列高最小的一列。其实这种方式使用什么方式布局都无所谓&#xff0c;我使用的是flexd布局。Flex的使用在这里就不讲解了&#xff0c;网上的教程一大堆。这里讲解使用V…

数据治理到底是什么?为什么要做数据治理?

数据治理的两个目标&#xff1a;一个是提质量&#xff0c;一个是控安全。通过业务流程优化&#xff0c;规范数据从产生、处理、使用到销毁的整个生命周期&#xff0c;使得数据在各阶段、各流程环节安全可控&#xff0c;合规使用。 数据治理治的是“数据”吗&#xff1f; 数据是…