Linux进程控制(进程终止+进程等待+进程程序替换)

news2024/11/15 8:21:33

文章目录:

  • 一、进程终止
    • 进程退出码
    • 常见的进程退出方法
      • exit函数
      • _exit函数
      • return
      • exit vs _exit vs return
  • 二、进程等待
    • 进程等待的必要性
    • 进程等待的方法
      • wait
      • waitpid - 从子进程获取状态信息
    • 如何获取子进程status
    • 进程的阻塞等待和非阻塞等待
  • 三、进程程序替换
    • 替换原理
    • 进程替换函数

一、进程终止

在使用 Linux 系统时,我们的进程可能会终止/退出,我们则需要弄清楚进程退出的原因。是正常退出还是故障退出。

进程退出的三种场景:代码运行完毕且结果正确、代码运行完毕且结果不正确、代码异常终止。

进程退出码

在 Linux 系统中,进程退出码(也称为返回码)表示命令或脚本执行后的响应,是可执行程序返回给父进程的代码。取值范围是 0 ~ 255 ,0 表示成功执行,范围内的任何非零退出码表示发生错误。我们可以使用退出码来了解在进程执行期间是否出现了错误或者其它意想不到的情况。🎭

当命令或脚本执行完成之后,我们可以使用 echo $? 来查看最近一个退出进程的退出码;

在这里插入图片描述

Linux 文档项目中有一个保留代码列表,它提供了针对特定场景使用哪些代码的建议。一下是 Linux 或 Unix 中的标准错误代码:

错误代码对应信息
1Catchall for general errors
2Misuse of shell builtins(according to bash documentation)
126Command invoked cannot execute
127Command not found
128Invalid argument to exit
128+nFatal error signal “n”
130Script terminated by Control-C
255\*Exit status out of range

程序退出码都有其特定的含义,可以帮助定位程序所出现的错误。这些退出码是可以由写程序的人自己定义其含义的,并不是固定的。

常见的进程退出方法

正常终止(可以通过 echo $? 查看进程退出码)

  • 从main函数中返回
  • 调用exit
  • 调用_exit

异常退出:

  • ctrl + c :信号终止

exit函数

在 Linux 系统下,exit 命令用于退出当前运行的 shell。它可以在 Linux CLI 中使用,也可以在 shell 或 bash 中使用。它接受一个参数 N,并返回状态 N 退出 shell。如果没有提供 N ,那么它返回最后执行的命令的状态。

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

在程序中,exit函数可以在代码的任意一个位置退出进程,在调用 exit 函数前,做了以下工作:

  • 执行用户通过 atexit 或 on_exit 定义的清理函数。
  • 关闭所有打开的流,所有的缓存数据均被写入。
  • 调用 _exit 函数。

以下代码中的 exit 函数在终止前会将缓冲区中的数据刷新出来:

在这里插入图片描述

_exit函数

_exit 和 exit 类似,可以使代码在任意位置终止,但是 _exit 会直接终止进程,不会对进程进行推出前的处理工作。

#include<stdio.h>
void _exit(int status);

// 参数:status 定义了进程终止状态,父进程通过 wait 来获取该值
// 说明:虽然 status 是 int,但是仅有低8位可以被父进程使用。所以 _exit(-1) 时,在终端执行 $? 时返回值是255.

以下代码演示使用 _exit 终止进程,进程结束,缓冲区中的数据不会被刷新出来:

在这里插入图片描述

return

return 是一种很常见的进程退出方法。执行 return num; 等同于执行 exit(num),因为调用 main 运行时函数会将 main 的返回值当作 exit 的参数。

exit vs _exit vs return

return 是关键字,exit() 和 _exit()是函数。🎯

return 表示函数返回,而 exit() 和 _exit() 表示程序的退出。return 和 exit 在 main 函数中的作用是一样的,退出程序并将返回值给操作系统。而在普通函数中,return 返回值给上层调用函数,exit 则退出程序并返回到操作系统。🎯

在 main 函数中,return 和 exit 都需要执行标准 I/O 库的清理、关闭流等,然后进入内核。而 _exit 函数是直接进入内核,不做任何清理相关操作。🎯
在这里插入图片描述

二、进程等待

进程等待的必要性

  • 子进程退出,如果父进程不管,那么可能造成 “僵尸进程的问题” ,从而造成内存泄漏。
  • 进程一旦变成僵尸状态,kill -9 命令也杀不掉,该进程就会一直处于僵尸状态。
  • 父进程需要知道派给子进程的任务完成得怎么样,结果是否正确等。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

进程等待的方法

wait

bash wait 命令是一个 shell 命令,用于等待后台运行进程完成并返回退出状态。与等待指定时间的 sleep 命令不同,wait 命令等待所有或特定的后台任务完成。

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

pid_t wait(int *status); // 等待子进程停止或者终止

返回值:wait 调用成功执行返回等待进程的 pid,失败则返回 -1。

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

如下所示,创建一个子进程,子进程运行并打印三次语句退出,父进程使用 wait 函数一直等待子进程退出并读取子进程的退出信息:

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 让子进程打印3次之后退出
        int count = 3;
        while(count)
        {
            printf("I am a child process! PID : %d --- %d\n",getpid(),count--);
            sleep(1);
        }
        exit(0);
    }

    int status = 0;
    printf("I am a parent process,Prepare to wait for the child process. PID : %d\n",getpid());
    pid_t ret = wait(&status);
    if(ret > 0)
    {
        printf("wait child process sucess!\n");
        if(WIFEXITED(status))
        {
            printf("exit code : %d\n",WEXITSTATUS(status));
        }
    }
    sleep(3);
    return 0;
}

这里我们可以使用监控脚本对进程所处的状态进行实时查看:

while :; do ps axj | head -1 && ps axj | grep test | grep -v grep | grep -v Ssl;echo "---------------------------------";sleep 1;done

运行结果如下:子进程退出之前,父子进程都处于 S+ 状态,当子进程退出后,父进程回收子进程。父进程等待三秒中之后,父进程也被回收。

在这里插入图片描述

waitpid - 从子进程获取状态信息

#include<sys/wait.h>

pid_t waitpid(pid_t pid,int *status,int options);

返回值:

当正常返回的时候 waitpid 返回收集到的子进程的进程 ID;
如果设置了选项 WNOHANG ,而调用中 waitpid 发现没有以退出的子进程可收集,则返回0 ;
如果调用出错,则返回 -1,这时 error 会被设置成相应的值以指示错误所在。

参数:

  • pid:
    pid = -1,等待任意一个子进程,与 wait 等效。
    pid > 0,等待其进程 ID 与 pid 相同的子进程。

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

  • options:
    WNOHANG:若 pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的 ID。

说明:

  • 若子进程已经退出,调用 wait/waitpid 时,wait/waitpid 会立即返回,并且释放资源,获取子进程的退出信息。
  • 若在任意时刻调用 wait/waitpid ,子进程存在且正在运行,则进程可能发生阻塞。
  • 若不存在该子进程,则立即出错返回。

如下所示,创建一个子进程,父进程使用 waitpid 等待子进程,等到子进程退出并读取子进程的退出信息:

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

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        fprintf(stderr,"Fork failed !\n");
        exit(-1);
    }
    else if(id == 0)
    {
        int count = 2;
        while(count)
        {
            printf("I am a child process!PID:%d --- %d\n",getpid(),count--);
            sleep(1);
        }
        printf("Done from child process!\n");
        exit(15); // 使用特殊退出码,观察父进程接收到的信息
    }
    else 
    {
        int status = 0;
        printf("I am a parent process.Prepare to wait for the child process!PID:%d\n",getpid());
        pid_t ret = waitpid(id,&status,0); // 这里的第三个参数暂时设置为0
        if(ret > 0)
        {
            if(WIFEXITED(status))
            {
                printf("Child process exit normally,exit code : %d\n",WEXITSTATUS(status));
            }
        }
    }
    return 0;
}

运行结果如下,进程正常退出,子进程的退出码和设置的一样。

在这里插入图片描述

如何获取子进程status

上面所说的 wait/waitpid 两个函数,其中都有一个参数 status,该参数是一个输出型参数,有操作系统填充。如果传入的参数 status 是 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会更具该参数,将子进程的退出信息反馈给父进程。status 虽然是一个整形变量,但是不能简单的当作整形来看待,可以当作位图来看,具体如下所示(只关注 status 的低16个比特位):
在这里插入图片描述若进程正常终止,则在 status 的16个比特位中,前8位表示进程的退出状态(进程退出码)。若进程非正常终止,而是被信号所杀,那么 status 的低7位表示终止信号,第8位则是 core dump 标志。

如何获取参数 status 的退出码和退出信号呢?

exitCode = (status>>8)&0xFF; // 进程退出码
exitSignal = status&0x7F; // 进程退出信号

系统为了简化使用成本,提供了两个宏来提取进程退出码和退出信号:

WIFEXITED(status):通过检查是否收到信号来判断进程是否正常退出。
WEXITSTATUS(status):获取进程的退出码。

🧩进程终止信号?
我们可以使用命令 kill -l 来查看终止信号列表:

在这里插入图片描述
当一个进程退出时,终止信号为0,说明进程是正常退出。若进程被信号所杀,那么对应的信号就是进程终止的原因。

注意:当进程由于信号原因退出时,这时的退出码是没有任何意义的。

进程的阻塞等待和非阻塞等待

阻塞和非阻塞是当进程访问数据时,根据 IO 操作的就绪状态不同而采取的不同处理方式,例如父进程等待子进程,阻塞方式下父进程会一直等到子进程退出读取完数据再继续往下执行;非阻塞方式下,父进程在等待子进程的过程中可以做自己的事,当子进程退出时,父进程再读取子进程的退出信息。

进程的阻塞等待方式:

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

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork false");
        exit(1);
    }
    if(id == 0)
    {
        printf("I am a child process,pid : %d\n",getpid());
        sleep(10);
        exit(17);
    }
    else 
    {
        int status = 0;
        int ret = wait(&status);
        if(ret>0&&(status&0x7F)==0) // 进程正常退出
        {
            printf("child process exit code : %d\n",(status>>8)&0xFF);
        }
        else if(ret > 0) //进程异常
        {
            printf("signal code : %d\n",status&0x7F);
        }
    }
    return 0;
}

进程的非阻塞等待方式:

如何让父进程进行非阻塞等待呢?在向 waitpid 传入参数时,将第三个参数传入 WNOHANG 。非阻塞等待时,若子进程没有结束,waitpid 函数将返回0,那么父进程不予以等待。当子进程结束时,waitpid 函数将返回子进程的 PID ,父进程则读取子进程的退出信息。

在这里插入图片描述

如下所示:

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

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
       printf("fork false\n");
       exit(1);
    }
    else if(id == 0)
    {
        printf("I am a child process! PID : %d\n",getpid());
        sleep(10);
        exit(1);
    }
    else 
    {
        // 基于非阻塞的轮询等待方案
        int status = 0;
        while(1)
        {
            pid_t ret = waitpid(-1,&status,WNOHANG);
            if(ret == 0)
            {
                printf("子进程还没有退出!父进程可以做其它事情!\n");
                sleep(1);
            }
            else if(ret == id&&WIFEXITED(status))
            {
                printf("wait child process success,exit code : %d,exit signal : %d\n",status&0x7F,(status>>8)&0xFF);
                break;
            }
            else 
            {
                printf("wait child failed,return.exit code : %d\n",status&0x7F);
                return 1;
            }
        }
    }
    return 0;
}

三、进程程序替换

替换原理

fork 创建子进程之后执行的是和父进程相同的程序(也可能执行的是不同的代码分支),子进程往往需要调用 exec 函数执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动进程开始执行。调用 exec 并不创建新的进程,所以调用 exec 之后依旧是原来的进程。

在这里插入图片描述

  • 进程替换不会创建新的进程,进程替换是将该进程的数据和代码替换为指定的可执行程序。而进程依旧是原来的进程,PCB没有改变。
  • 进程替换成功之后,替换函数之后的代码不会执行,因为进程替换将原来的整个代码和数据覆盖了。替换之后原来的代码就没了,若进程替换失败,则替换函数之后的代码任然可以执行。
  • 进程替换成功后,退出码为替换后的进程退出码。
为什么需要进行进程程序替换?

fork 创建子进程后,创建的子进程要么和父进程执行相同的代码片段,要么执行不同的代码分支。但是这样的使用不具有灵活性。例如:使用 c++ 程序将 Java 程序调动起来。即很多的程序功能已经用另外的程序写好了,就不需要在父进程中控制子进程执行不同的代码分支,可以直接用一个已有的程序替换掉子进程,使子进程完全执行所替换程序的功能。

子进程进行程序替换,会不会影响父进程的代码和数据?

不会。子进程被创建后与父进程共享代码和数据,但当子进程进行代码和数据的更改或写入时,这时使用写时拷贝的方式分离父子进程的相应代码。因此,当子进程进行程序替换时,意味着子进程对数据和代码进行写入,因此需要将与父进程共享的代码和数据进行写时拷贝,分离父子进程的代码。所以子进程执行进程程序替换是不会影响父进程的。

进程替换函数

Linux 中的 exec 命令用于从 bash 本身执行命令。此命令不会创建新的进程,它只是将 bash 替换为要执行的命令。若 exec 命令执行成功,则不返回调用进程。在 bash 和 ksh 等 shell 中,它还用于重定向一个完整会话或整个脚本的文件描述符。

exec 函数族说明:
当我们需要子进程去执行另外的程序时,exec 函数就提供了在一个程序中调动另外一个程序的方法(进程需要调用 exec 函数族中某一个函数来进行程序替换)。当调用 exec 函数时,该进程的代码和数据完全被新程序替换,从新程序的启动例程开始执行。

其中有六种以 exec 开头的函数,统称为 exec 函数:

在这里插入图片描述

下面,我们以 execl 为例子来讲解一下使用方法:

int execl(const char *path,const char *arg, ...);

第一个参数传入的是你将要替换的进程的路径(首先需要找到所替换的程序的路径),第二个参数是可变参数列表(怎么执行这个程序,即携带选项),需要注意的是,exec 函数族需要以 NULL 进行结尾。

代码示例如下:使用 execl 函数调用 ls 命令。

在这里插入图片描述

接下来我们说明一下每一个函数如何使用:

// 带l的,需要将执行程序的选项一个个列出来,最后以NULL结尾
// 带p的,可以自己使用环境变量PATH找到路径,无需写路径
// 带e的,可以自己导入环境变量

int execl(const char* path,const char* arg,...);
execl("/usr/bin/ls","ls","-al",NULL);

int execlp(const char* file,const char* arg,...);
execlp("ls","ls","-al",NULL);

int execle(const char* path,const char* arg,...,char* const envp[]);
char* envp_[] = {MYENV=1234,NULL}; //这是自己设置的环境变量
execle("./process","process",NULL,envp_); 

int execv(const char* path,char* const argv[]);
char* argv_[] = {"ls","-a","-l",NULL};
execv("/usr/bin/ls",argv_);

int execvp(const char* file,char* const argv[]);
char* argv_[] = {"ls","-a","-l",NULL};
execvp("ls",argv_);

int execvpe(const char* file,char* const argv[],char* const envp[]);
char* argv_[] = {"process",NULL};
char* envp_[] = {"MYENV=1234",NULL};
execvpe("process",argv_,envp_);

还有一个特殊的:execve

在这里插入图片描述

此接口和上面的六个接口使用方法是一样的,此接口是系统接口,上面的接口都是对系统接口的封装。所以 execve 在 man 手册的第2节,其它函数在第 3 节。设计这么多接口的意义在于,在面对不同场景时可以选择合适的接口,简化使用的成本。

在这里插入图片描述

函数解释:

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用失败则返回-1。
  • 因此调用 exec 函数只有出错才有返回值,调用成功是没有返回值的,因为调用成功程序已经被替换了。

命名解释:
exec 族的函数有7个,我们应该怎样才能记住这些函数呢?实际上每一个函数的命名都是有规律的,它们都以 exec 开头,后面的字符代表下面的意义,理解了就记住了。

  • 【l】 list:表示传参数需要使用列表的形式
  • 【v】vector:使用数组进行传参
  • 【p】path:p可以自动搜索环境变量PATH,不需要具体传路径
  • 【e】env:表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表
execlp列表
execle列表否,需要自己组装环境变量
execv数组
execvp数组
execve数组否,自己组装环境变量

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

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

相关文章

科技制造商必须对安全、设计选择承担更多责任

网络安全和基础设施安全局局长称当今商业网络安全的现状是"不可持续的"&#xff0c;公司、消费者和政府必须集体转变期望&#xff0c;让主要软件和硬件制造商对不安全的产品负责&#xff0c;而不是用户。 拜登政府预计将在未来几天发布一项战略&#xff0c;该战略将…

burp联动xray进行被动扫描

burp联动xray进行被动扫描0、简介1、打开burp&#xff0c;设置转发数据包&#xff0c;将burp抓到的数据包&#xff0c;额外转发到本机的7777端口2、出现这个代表配置成功3、接着在xray里输入这串代码&#xff0c;让xray进行监听本机的7777端口&#xff0c;进行被动扫描&#xf…

操作系统笔记、面试八股(二)—— 死锁

文章目录2. 死锁2.1 死锁的必要条件2.2 死锁预防2.3 死锁避免2.3.1 银行家算法2.4 死锁检测与死锁解除2.4.1 进程-资源分配图2.4.2 死锁检测的步骤2.4.3 死锁解除方法2. 死锁 2.1 死锁的必要条件 互斥 资源必须处于非共享模式&#xff0c;即因此只能有一个进程访问。如果有另一…

电脑崩溃蓝屏问题如何重装系统

电脑是我们日常生活和工作中必不可少的工具&#xff0c;但在使用过程中&#xff0c;难免会遇到各种问题&#xff0c;例如系统崩溃、蓝屏、病毒感染等&#xff0c;这些问题会严重影响我们的使用体验和工作效率。而小白一键重装系统可以帮助我们快速解决这些问题&#xff0c;本文…

精选博客系列|加速基于同态加密的隐私保护机器学习

随着机器学习在当今的企业和软件平台中的广泛使用&#xff0c;跨人工智能 &#xff08;AI&#xff09; 平台的隐私保护技术的解决方案也显得非常重要。虽然这个想法在今天看起来很明显&#xff0c;但人工智能研究社区历来更专注于打破数据孤岛的界限&#xff0c;并将数据从一个…

在线就能用的主图设计素材,免费分享!

如何给自己的店铺商品设计专属的节日活动主图&#xff1f;没有节日活动的主体素材要如何设计&#xff1f;下面小编就分享一个在线素材设计工具&#xff0c;打开乔拓云&#xff0c;平台里面不仅有许多能使用的电商设计素材&#xff0c;还有许多的设计工具和模板能帮助你快速的实…

macOS 13.3 Beta 2 (22E5230e)With OpenCore 0.8.9正式版 and winPE双引导分区原版镜像

原文地址&#xff1a;http://www.imacosx.cn/112340.html&#xff0c;转载需注明出处镜像特点完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要…

海外服务器:为什么越来越多的人选择跨境托管?

在数字化时代&#xff0c;越来越多的企业和个人需要一个高效、稳定、安全的网络服务。而以独立服务器为主的海外服务器和跨境托管服务已成为满足这种需求的重要选择。在本文中&#xff0c;小编将探讨海外服务器和跨境托管的优势和发展趋势。一、什么是海外服务器和跨境托管?海…

如何使用GitBleed从Git库镜像中提取数据

关于GitBleed GitBleed是一款针对Git库镜像的安全检测工具&#xff0c;该工具包含了多个Shell脚本&#xff0c;可以帮助广大研究人员下载克隆的Git库和Git库镜像&#xff0c;然后从中提取各种数据&#xff0c;并分析两者之间的不同之处。 功能介绍 工具提供的脚本能够克隆指…

Django实践-02创建应用

文章目录Django实践-02创建应用Django介绍Django项目构建Django项目安装&#xff08;前一篇已经装好&#xff09;&#xff1a;创建应用1. 创建app&#xff0c;执行下面的命令&#xff1a;2. 修改视图views.py3. 修改Django项目目录下的urls.py文件4.重新运行项目5. 基于模板完成…

深度包检测(DPI)详细介绍

以前不了解这个&#xff0c;一个应聘职位是这个方面的&#xff0c;就在网上收集这个资料&#xff0c;了解了这个方面的资料&#xff0c;其实&#xff0c;这个核心是自然语言识别。 目录 简介背景 流量识别 常用功能具体功能 做法特征识别架构举例部署方式 串接方式并接方式存…

Mac安装Redis后的配置

Mac安装Redis后的配置找到安装目录配置文件修改启动服务做测试连接找到安装目录 1.双击&#xff08;右键&#xff09;访达&#xff0c;点击前往文件夹进行查找&#xff0c;如下图所示 2.然后再如下图所示中找到如下路径&#xff1a; usr/local/bin3.找到redis.conf即redis的…

JAVA版B2B2C商城源码多商户入驻商城

三勾商城多商户是开发友好的微信小程序商城&#xff0c;框架支持SAAS&#xff0c;支持发布 iOS Android 公众号 H5 各种小程序&#xff08;微信/支付宝/百度/头条/QQ/钉钉/淘宝&#xff09;等多个平台&#xff0c;不可多得的二开神器&#xff0c; 为大中小企业提供极致的移…

hadoop的运行模式

作者简介&#xff1a;大家好我是小唐同学(๑>؂<๑&#xff09;&#xff0c;好久不见&#xff0c;为梦想而努力的小唐又回来了&#xff0c;让我们一起加油&#xff01;&#xff01;&#xff01; 个人主页&#xff1a;小唐同学(๑>؂<๑&#xff09;的博客主页 目前…

SWM181 外部中断(EXTI)功能使用

SWM181 外部中断&#xff08;EXTI&#xff09;功能使用&#x1f4cc;SDK固件包&#xff1a;https://www.synwit.cn/kuhanshu_amp_licheng/ &#x1f4d6;中断案例&#xff1a; &#x1f33c;开发板如下图&#xff1a; ✨注意新手谨慎选择作为入门单片机学习。 GPIO特性介…

代码随想录-49-111.二叉树的最小深度

目录前言题目1.层序迭代思路2. 本题思路分析&#xff1a;3. 算法实现4. pop函数的算法复杂度5. 算法坑点前言 在本科毕设结束后&#xff0c;我开始刷卡哥的“代码随想录”&#xff0c;每天一节。自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下。 代码随想录此题链接 …

从功能测试进阶自动化测试涨薪10k,只用了1个月...

在开始正文前&#xff0c;先给大家一碗鸡汤&#xff1a;“你要坚信时间是不会辜负每一个努力付出的人&#xff0c;当你拥有一万次的训练和沉淀后&#xff0c;生活也会还你诗和远方。” 现在有很多人经常被“想要快速看到好的结果”的“快”思维裹挟着&#xff0c;稍微在短时间…

循环删除 List 中的元素

引用&#xff1a;https://mp.weixin.qq.com/s/j3k70kB3KPGO5ZKpOxJ4Pw 问题 比如有以下这个 List&#xff1a; public List<String> initList Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白&…

【微电网_储能】基于启发式状态机策略和线性程序策略优化方法的微电网中的储能研究【给定系统约束和定价的情况下】(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

Share Creators确认参展GDC 2023,致力于解决游戏开发者数字资产管理难题

全球游戏行业年度盛会“2023 游戏开发者大会”&#xff08;Game Developers Conference&#xff0c;简称 GDC&#xff09;&#xff0c;是全球规模最大的游戏开发者盛会&#xff0c;将于 2023 年 3 月 20 至 24 日于美国旧金山举行。自 1988 年首次举办以来&#xff0c;本届 GDC…