【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){// childint 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 {// parentint 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:0xFF00printf("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){// childint 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.mzph.cn/news/659864.shtml

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

相关文章

新春送福,暖心探访

春节将至&#xff0c;岁寒情暖。为了深入了解辖区困境老人的生活状况和真实需求&#xff0c;让困境老人感受到党和政府的关爱与温暖&#xff0c;营造幸福和谐的节日气氛。2024年1月31日下午&#xff0c;在长沙市湘江新区民政与社会保障局的支持下&#xff0c;学联社区携手长沙市…

Https加密超文本传输协议的运用

一、https的相关知识 1.1 https的简介 HTTPS &#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure &#xff09;&#xff0c;是以安全为目标的 HTTP 通道&#xff0c;在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加…

基于微服务的高考志愿智能辅助决策系统(附源码)

目录 一.引言 1、编写目的 2、系统功能概述 二.功能分析 三.微服务模块 1、微服务用户相关模块 &#xff08;1&#xff09;用户注册 &#xff08;2&#xff09;用户登录 &#xff08;3&#xff09;用户信息管理 &#xff08;4&#xff09;用户操作 2、微服务文件云存…

React Router 完美教程(下)

我们书接上回&#xff0c;继续我们的React Router 路由之路&#xff1a; 我们到目前为止都没有用到 state、useEffect、redux等状态管理器。但也达到了我们的设计目的。 注意&#xff0c;action 返回的结果 可以在组件中使用 useActionData() 来获取。就像 useLoaderData() 的使…

MD5算法:高效安全的数据完整性保障

摘要&#xff1a;在数字世界中&#xff0c;确保数据完整性和安全性至关重要。消息摘要算法就是一种用于实现这一目标的常用技术。其中&#xff0c;Message Digest Algorithm 5&#xff08;MD5&#xff09;算法因其高效性和安全性而受到广泛关注。本文将详细介绍MD5算法的优缺点…

web应用课——(第四讲:中期项目——拳皇)

代码AC Git地址&#xff1a;拳皇——AC Git链接

87 SpringMVC 上传文件在业务代码中拿不到文件数据

前言 呵呵 最近在整理文件上传部分的东西的时候, 发现了一个问题 文件上传部分 有一些基础的问题, 可以参见 29 SpringMVC 上传文件未生成临时文件, 我们这里上传的文件的大小是 大于 sizeThreshold 的 SpringMVC 上传文件的时候会生成一个临时文件, 我想直接使用这个临时…

【开源】SpringBoot框架开发海南旅游景点推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏5(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言修改鼠标光标和中心提示图鼠标光标素材修改默认鼠标光标修改中心提示图 拾取提示弹窗简单绘制UI拾取弹窗功能 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使…

canvas的一些基础

在 Canvas 中&#xff0c;基本图形有两种&#xff1a;直线图形和曲线图形 直线图形&#xff1a;直线、矩形(描边矩形和填充矩形)、多边形 曲线图形&#xff1a;曲线和弧线&#xff08;弧线是圆的一部分&#xff0c;曲线则不一定&#xff0c;弧线上的每个点都具有相同的曲率&…

十分钟快速上手Spring Boot与微信小程序API接口的调用,快速开发小程序后端服务

1.1 微信小程序API接口介绍 微信小程序API接口是连接小程序前端与后端服务器的桥梁&#xff0c;它提供了丰富的功能接口&#xff0c;包括用户信息、支付、模板消息、数据存储等。这些API接口能够满足开发者在小程序中实现各种复杂业务逻辑的需求。 用户信息接口 用户信息接口…

计算机服务器中了locked勒索病毒怎么办,locked勒索病毒解密流程

随着网络技术在企业生产生活中的应用&#xff0c;越来越多的企业开始走向数字化办公模式&#xff0c;极大地提升了企业办公与生产效率&#xff0c;而其中的企业数据起到了关键性作用&#xff0c;企业的数据安全是众多企业关心的话题。但网络是一把双刃剑&#xff0c;近期&#…

【C++】开源:Windows图形库EasyX配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Windows图形库EasyX配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#…

【 USRP 相控阵】ADAR1000 : 4 通道 X 频段和 Ku 频段波束形成器,8 GHz 至 16 GHz

介绍 ADAR1000 是一款适用于相控阵的 4 通道 X 和 Ku 频段波束形成内核芯片。此器件在接收和发射模式之间以半双工状态工作。在接收模式下&#xff0c;输入信号通过四个接收通道后在公共 RF_IO 引脚上组合在一起。在发射模式下&#xff0c;RF_IO 输入信号拆分后通过四个发射通…

智慧食堂预点餐管理系统-计算机毕业设计源码48846

摘要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;餐饮业当然也不例外。智慧食堂预点餐管理系统小程序是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&…

回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于OOA-LSSVM鱼鹰算法优化最小支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基于OOA-LSSVM鱼鹰算法优化最小…

C++关键词auto详解

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、小思考 随着我们对于C的不断学习&#xff0c;遇到的程序越来越复杂&#xff0c;程序中用到的类型也越来越复杂…

java接口练习

首先&#xff0c;明确&#xff1a;接口可以提供模糊的方法&#xff0c;方案&#xff1b;那么具体的实现需要另外创建实现类去实现&#xff1b; 另外&#xff1a;明确接口的特点&#xff1a;接口回调&#xff0c;接口的多态性&#xff1b; 具体解释&#xff1a; 接口的特点&a…

转转基于MQ的分布式重试框架设计方案

文章目录 1 背景2 方案3 效果4 可选项5 注意事项6 总结 1 背景 在分布式场景下&#xff0c;为了保障系统的可用性和数据的最终一致性&#xff0c;采用基于消息队列&#xff08;MQ&#xff09;的重试机制是一种常见的解决方案。伪代码如下&#xff1a; /*** 需要保证最终一致性…

phpstudy安装并运行redis

对于一个菜鸟来说&#xff0c;任何一个小步骤都可能研究半天&#xff0c;比如“phpstudy安装并运行redis”这一问题&#xff0c;解决好后第一时间记录下来&#xff0c;方便日后查看&#xff0c;也为遇到同样困难的小伙伴提供个参考&#xff01; 一、phpstudy安装redis 1.打开…