【Linux从入门到精通】进程的控制(进程退出+进程等待)

  

  本篇文章主要讲述的是进程的退出和进程等待。希望本篇文章的内容会对你有所帮助。

文章目录

一、fork创建子进程

1、1 在创建子进程中操作系统的作用

1、2 写时拷贝

二、进程终止

2、1 常见的进程退出

2、2 进程的退出码

2、2、1 运行结果正确实例 

2、2、2 运行结果不正确实例

2、2、3 代码运行异常

2、3 常见的进程退出方式

三、进程等待

3、1 进程等待的引入

3、2 等待的方法

3、2、1 wait 方法

3、2、2 waitpid 方法

四、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:进程控制💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️  

一、fork创建子进程

  我们知道fork是创建子进程的。创建的子进程,在我们不做任何操作的情况下,子进程有数据和代码吗?操作系统都做了哪些事情呢?

1、1 在创建子进程中操作系统的作用

  我们先想一下,创建一个子进程本质上在是干啥?是不是系统中多了一个进程呢!进程是由所对应的代码和数据,再加上内核数据结构构成的。

  那一切都就很容易理解了。创建子进程,就是给子进程分配自己的内核数据结构。同时,把部分所需代码和数据加载到进程当中。我们父进程的代码和数据一般都是从磁盘加载(我们自己所写的代码)过来的。子进程的代码和数据哪里来的呢?

  因为在加载的过程中,子进程并没有自己的代码和数据,所以子进程只能使用父进程的代码和数据了!也就是子进程和父进程共享一份数据!

  我们知道,每个进程都是具有独立性的,进程之间不会相互影响。那子进程和父进程共享一份代码和数据,不管是子进程还是父进程去修改数据,那不就影响到了另一个进程吗?因为代码和数据是共享的。但是,事实并不是不会相互影响。呢么做到的不相互影响呢?

  我们为了保持进程的独立性,就采用了写时拷贝技术。那么我们接下来先了解一下写时拷贝技术。

1、2 写时拷贝

  我们知道子进程和父进程共享一份代码和数据,那么只要有任何一进程对其数据进行修改,就会对该数据进行深拷贝,再进行修改。这就很好的解决了相互影响的问题。这就是写时拷贝,只有在对其数据进行修改时,才对数据开空间,进行深拷贝

  写时拷贝就是一种拖延技术,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

  引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

  那么这里有一个问题:为什么在创建子进程时,不直接对数据进行深拷贝,而是采用与父进程共享一份代码和数据呢?

  首先,我们所创建的子进程可能就不会用到数据空间,即使用到了,也可能是只读,并不会对其进行修改而影响到进程的独立性。其次,操作系统也无法预知那些空间会被写入。所以操作系统选择写时拷贝技术,会有以下好处:

  • 可以很好的分离父子进程的数据,保证进程的独立性;
  • 用时才分配空间,合理充分利用了内存,是高效使用内存的一种表现。

二、进程终止

2、1 常见的进程退出

   我们常见的进程退出有如下三种:

  1. 代码运行完毕,结果正确;
  2. 代码运行完毕,结果不正确;
  3. 代码异常终止。
关键是我们怎么知道进程运行结果正确还是不正确呢?这时候就需要了解一下 进程的退出码了

2、2 进程的退出码

  进程的退出码(Exit Code)是一个整数值,用于表示进程在终止时返回给操作系统的状态信息。退出码提供了有关进程是否成功执行和执行结果的信息。如果出错,同时也方便我们地位错误的原因。

2、2、1 运行结果正确实例 

  我们平常写代码中也一直在用到退出码——return 0。在main函数中,return 的返回值就算是进程的退出码。我们可在linux下查看一下。linux下查看最近进程的退出码的命令是:echo $?。实例代码如下:

#include<iostream>    using namespace std;    int main()    
{    cout<<"正常运行"<<endl;    return 0;    
} 

  我们再来查看一下改进程的退出码,结果如下:

  那要是我们设置 return 的返回值为100呢?我们再来看看结果:

2、2、2 运行结果不正确实例

  我们可通过一个判断来完成结果是否正确。代码如下:

int main()    
{    const int N=100;    int ret=0;    for(int i=0;i<N;i++)                                                                                                                                     {    ret+=i;    }    if(ret!=5050)    return 1;    return 0;    
}    

  我们知道,1到100的和为5050。我们在这里故意把答案写错来判断一下,结果如下:

  

  运行结果不正确的原因有很多,我们可通过sterror函数将它们打印出来,结果如下:

   我们发现,一共是由133种运行错误的结果。我们不妨来验证一下:

  退出码是2,是不是与我们刚刚打印出的对上了! 

2、2、3 代码运行异常

  我们先看如下代码:

int main()
{cout<<"hello linux"<<endl;cout<<"hello linux"<<endl;    cout<<"hello linux"<<endl;    int* p=NULL;*p=1;cout<<"hello linux"<<endl;cout<<"hello linux"<<endl;cout<<"hello linux"<<endl;return 0;
}

  上述代码是对空指针进行了访问并且修改,必然会导致程序崩溃。结果如下:

 

  我们看到:Segmentation fault。我们再看退出码是139。当程序崩溃时,退出码毫无意义,并且一般情况下 return 语句都不会别执行!

2、3 常见的进程退出方式

  我们平常再写代码的时候,如何用代码终止一个进程呢?其实在main() 函数中的return语句,就是终止进程的。return 的值就是退出码,返回给操作系统。那还有其他的方式吗?答案是有的。我们接着往下看。

  可能我们大家在平常中也会用到exit()函数。exit在代码的任何地方调用,都是表示终止进程。那我们来简单测试一下exit()函数。代码如下:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    int main()    
{    printf("hello linux");    sleep(3);    exit(111);                                                                                                                                               return 0;    
}

  我们上面打印的字符串,并没有选择通过 \n 或者fflush 将其在缓冲区刷新出来,而是休眠了3秒。那当exit 终止程序时,会将其刷新出来吗?我们看如下运行结果:

  通过上述发现,在exit 终止程序是,会刷新缓冲区。退出码也是我们所设置的 111 。 

  其实还有一个函数,_exit() 也是用来终止程序的。我们不妨也来简单测试一下 _exit() 函数,看是否与 exit() 函数相同。代码如下:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    int main()    
{    printf("hello linux");    sleep(3);    _exit(111);                                                                                                                                               return 0;    
}

  运行结果如下:

  我们发现,_exit() 函数并没有刷新缓冲区!

  exit() 函数时C语言的库函数,_exit() 是系统的接口。其实exit() 底层也是调用的_exit(),但是在调用_exit() 之前,还做了一些其他的工作:

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

  那么提问:printf 所打印的数据并不是直接向输出设备输出,而是保存在了缓冲区。那么这个缓冲区是在哪里呢?是由谁来维护的呢?

  我们先看如下图片:

  _exit() 函数直接调的是操作系统內部的函数。如果上述的缓冲区是在操作系统內部的话,_exit() 函数也会把数据刷新出来。但是是并没有。从这点也可以说明,上述的缓冲区并不是在操作系统內部。显而易见,缓冲区是由C语言标准库函数来维护的。

  这里对exit() 和 _exit()函数进行总结。exit()和_exit()都是用于退出程序的函数,但在使用方式和功能上存在一些区别。

  exit()是C语言标准库中的函数,在C++中也可以使用。它执行以下操作:

  1. 调用终止处理程序(atexit函数注册的函数)。
  2. 刷新并关闭stdin、stdout和stderr流。
  3. 调用各个注册函数进行清理工作。
  4. 通过调用C库实现的_exit()系统调用来终止进程。

  _exit()是一个系统调用,用于直接终止进程,不会进行任何清理工作。它执行以下操作:

  1. 立即终止进程,并不执行后续的清理工作。
  2. 不会刷新缓冲区或关闭文件描述符,也不会执行终止处理程序。
  3. 可以带有一个整数参数,表示退出状态,将该值传递给操作系统。

  因此,exit()更安全,它可以确保执行所有的清理工作,并且允许某些回调函数得到执行的机会。_exit()则是一种粗暴的退出方式,立即终止进程,没有机会执行任何清理工作。

三、进程等待

3、1 进程等待的引入

  在进程的状态中讲到中讲到,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

  最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对, 或者是否正常退出。那么父进程怎么获取这些信息呢?
  父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。这也是父进程等待的原因!那父进程是如何等待的呢?

3、2 等待的方法

3、2、1 wait 方法

  进程等待函数wait()是在操作系统中用于等待子进程结束并获取子进程的状态信息的函数wait()函数用于等待任意一个子进程终止,并可以获取子进程的终止状态。我们再来看一下wait的使用方法:

  上图我们可到使用wait需要引入的头文件。同时wait的返回值: 成功返回被等待进程pid,失败返回-1;参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

  下面讲述waitpid()函数时,会讲到status。我们再来看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){perror("fork");exit(1); //标识进程运行完毕,结果不正确}else if(id == 0)                                                                                     {                                                                                                    //子进程                                                                                         int cnt = 5;                                                                                     while(cnt)                                                                                       {                                                                                                printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt--, getpid(), getppid());             sleep(1);}exit(0);                                                                                                                                             }                                   else               {                //父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());pid_t ret=wait(NULL);if(ret>0){printf("等待成功!,%d\n",ret);}}return 0;
}

  运行结果如下:

  观察上图发现,父进程打印出第一句话后,就不再往后执行。没错,wait()就是阻塞式等待。就是当子进程退出时,wait()回收子进程的资源和退出信息。当子进程一直在运行不退出,wait()就一直处于阻塞状态进行等待。

  我们不妨来验证一下,wait()到底回收子进程的资源了吗。验证的代码如上述验证wait()的代码几乎一样,只不过是当wait()等待成功后,不让父进程退出,接着打印。我们看如下运行结果:

  通过上图,我们看到当子进程推出后,父进程仍然在打印,并没有退出。如果父进程不对子进程进行回收,子进程就会进入僵尸状态。我们在查看的时候,子进程并没有进入僵尸状态,而是被回收了。 我们接下来再看一下waitpid的使用方法。

3、2、2 waitpid 方法

   waitpid()函数用于等待指定进程ID的子进程终止,也可等待任意子进程的终止。我们先来看看怎么使用waitpid。如下图:

  返回值: 当正常返回的时候waitpid 返回收集到的子进程的进程 ID ; 如果设置了选项WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可收集 , 则返回 0 ;如果调用中出错, 则返回 -1, 这时 errno 会被设置成相应的值以指示错误所在;
参数:
  • pid: pid=-1,等待任一个子进程,与wait等效Pid>0.等待其进程IDpid相等的子进程
  • status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出);WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options: WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID0表示阻塞式等待

  获取子进程status的方式:

  • wait waitpid ,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL ,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究 status 16 比特位)

  我们结合代码一起理解一下waitpid()和  获取子进程status。代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int code = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(1); //标识进程运行完毕,结果不正确}    else if(id == 0)    {    //子进程    int cnt = 5;    while(cnt--)                                                                                                                                           {    printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());    sleep(1);    }    exit(0);  }else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!if(ret > 0){// 0x7F -> 0000.000 111 1111printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\ret, status & 0x7F ,(status >> 8)&0xFF); //0xff --> 0000...000 1111 1111}}
}

  运行结果如下:

  父进程也可通过如下方式看子进程是否正常终止,和获取子进程的退出码:

    if(WIFEXITED(status)) //是否正常终止子进程{//子进程是正常退出的printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status)); //获取退出码}else{printf("子进程异常退出: %d\n", WIFEXITED(status));}

  我们再来看一下waitpid()进行非阻塞等待。 第三个参数就是:WNOHANG。我们知道Linux使用C语言写的,waitpid()是系统调用接口,也就是操作系统调用自己内部的函数。这个函数就是用C语言写的函数,全部是大写的WNOHANG是什么呢?宏定义!!!WNOHANG的值就是1,为什么不直接写1呢?因为可能过一段时间,我们就不知道1在这里是什么意思了,这类数字也被称为魔鬼数字/魔术数字WNOHANGWait No HANG。也就是没有夯住。"夯住"通常指的是一个进程或者应用程序无法继续正常执行,似乎被卡住了或者不再响应用户的输入或命令。这种情况也被称为"进程僵死"或"进程挂起"。

  我们通过如下代码测试一下非阻塞等待:

#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>typedef void (*handler_t)(); //函数指针类型std::vector<handler_t> handlers; //函数指针数组void fun_one()
{printf("这是一个临时任务1\n");
}
void fun_two()
{printf("这是一个临时任务2\n");
}// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!                                                                 
void Load()
{handlers.push_back(fun_one);handlers.push_back(fun_two);
}int main()
{pid_t id = fork();if(id == 0){// 子进程int cnt =  5;while(cnt){printf("我是子进程: %d\n", cnt--);sleep(1);}exit(11); // 11 仅仅用来测试}else{int quit = 0;                                                                                                                                        while(!quit){int status = 0;pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待if(res > 0){        //等待成功 && 子进程退出    printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));    quit = 1;    }    else if( res == 0 )    {    //等待成功 && 但子进程并未退出    printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");    if(handlers.empty())    Load();    for(auto iter : handlers)    {    //执行处理其他任务    iter();    }    }    else    {    //等待失败    printf("wait失败!\n");    quit = 1;    }    sleep(1);    }    }    return 0;                                                                                                                                                
} 

  waitpid函数不管子进程是否运行结束,都会有一个返回值。返回值大于0:等待成功 且 子进程退出。返回值等于0:等待成功 且 子进程没有退出。返回值小于0:等待失败。当我们知道子进程并没投退出时,父进程还可做一些其他事情,直到子进程退出。我们看运行结果:

  从上述的运行结果中可看出,在子进程没有退出的情况下,父进程也可做一些其他事情。退出码为11是我们自己设置的。 

四、总结

  子进程的退出状态信息返回给了操作系统中的进程控制块内,为什么wait和waitpid能够拿到子进程的退出状态呢?wait和waitpid是系统调用接口, 就是操作系统內部的函数!!!当然可以拿到了。设置全局变量行吗?答案是不行的。一旦对全局变量修改,就会发生写时拷贝。同时信号也无法处理。

  由于内容较多,分为两篇文章来整理。本篇文章主要讲述的是进程的退出和进程等待,下篇文章会讲到进程的替换。感谢阅读ovo~

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

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

相关文章

购物车功能实现(小兔鲜儿)【Vue3】

购物车 流程梳理和本地加入购物车实现 购物车业务逻辑梳理拆解 整个购物车的实现分为两个大分支, 本地购物车操作和接口购物车操作由于购物车数据的特殊性,采取Pinia管理购物车列表数据并添加持久化缓存 本地购物车 - 加入购物车实现 添加购物车 基础思想&#xff1a;如果…

高算力AI模组前沿应用:基于ARM架构的SoC阵列式服务器

本期我们带来高算力AI模组前沿应用&#xff0c;基于ARM架构的SoC阵列式服务器相关内容。澎湃算力、创新架构、异构计算&#xff0c;有望成为未来信息化社会的智能算力底座。 ▌性能优势AI驱动&#xff0c;ARM架构服务器加速渗透 一直以来&#xff0c;基于ARM架构的各类处理器…

python 字符串操作

1.字符串的使用 1.1 字符串的截取 str len1800 截取字符串中数字&#xff0c;并转化为数字 str1 str[4:] #得到字符串 1800&#xff0c; num eval(str1) #将字符串转换为数字&#xff0c;eval 用于比较复杂的情况&#xff0c;也可以直接用int(str1) #eval用于更复杂…

mybatisplus映射解读

目录 自动映射 表映射 字段映射 字段失效 视图属性 Mybatis框架之所以能够简化数据库操作&#xff0c;是因为他内部的映射机制&#xff0c;通过自动映射&#xff0c;进行数据的封装&#xff0c;我们只要符合映射规则&#xff0c;就可以快速高效的完成SQL操作的实现。既然…

STM32 Flash学习(一)

STM32 FLASH简介 不同型号的STM32&#xff0c;其Flash容量也不同。 MiniSTM32开发板选择的STM32F103RCT6的FLASH容量为256K字节&#xff0c;属于大容量产品。 STM32的闪存模块由&#xff1a;主存储器、信息块和闪存存储器接口寄存器等3部分组成。 主存储器&#xff0c;该部分…

3分钟学会设计模式 -- 单例模式

►单例模式 ►使用场景 在编写软件时&#xff0c;对于某些类来说&#xff0c;只有一个实例很重要。例如&#xff0c;一个系统中可以存在多个打印任务&#xff0c;但是只能有一个正在工作的任务&#xff1b;一个系统中可以多次查询数据库&#xff0c;但是只需要一个连接&#x…

Dart - 语法糖(持续更新)

文章目录 前言开发环境中间表示语法糖1. 操作符/运算符&#xff08;?./??/??/../?../.../...?&#xff09;2. 循环&#xff08;for-in&#xff09;3. 函数/方法&#xff08;>&#xff09;4. 关键字&#xff08;await for&#xff09; 最后 前言 通过将dill文件序列化…

HTML快速学习

目录 一、网页元素属性 1.全局属性 2.标签 2.1其他标签 2.2表单标签 2.3图像标签 2.4列表标签 2.5表格标签 2.6文本标签 二、编码 1.字符的数字表示法 2.字符的实体表示法 三、实践一下 一、网页元素属性 1.全局属性 id属性是元素在网页内的唯一标识符。 class…

【使用深度学习的城市声音分类】使用从提取音频特征(频谱图)中提取的深度学习进行声音分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringMVC-mybatis中可以返回查询的个数,但是都为null。。。

通过postman测试请求时&#xff0c;显示查询成功&#xff0c;返回一个json数组&#xff0c;里面也有数据&#xff0c;但是数据都是null。 说明&#xff1a;确实是sql执行成功了&#xff0c;只不过是没有将sql中的字段的值给注入到对象的属性中去。。。 Select("SELECT * …

cv2抛出异常 “install libgtk2.0-dev and pkg-config, then re-run cmake or configure”

背景&#xff1a; linux中使用cv2显示图片的时候&#xff0c;运行提示异常&#xff1a; 处理方式&#xff1a; 网友的推荐操作&#xff1a; 切换至root模式安装 apt-get install libgtk2.0-dev进入OpenCV下载目录&#xff0c;重新编译 cd /home/XXX/opencv mkdir release …

项目2 | 负载均衡式在线OJ

啊我摔倒了..有没有人扶我起来学习.... &#x1f471;个人主页&#xff1a; 《 C G o d 的个人主页》 \color{Darkorange}{《CGod的个人主页》} 《CGod的个人主页》交个朋友叭~ &#x1f492;个人社区&#xff1a; 《编程成神技术交流社区》 \color{Darkorange}{《编程成神技术…

pytorch2.x 官方quickstart测试

文章目录 1.本地环境2.[安装pytorch](https://pytorch.org/get-started/locally/) (Windows GPU版本&#xff09;3. [官方quickstart](https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html) 1.本地环境 D:\python2023>nvidia-smi Thu Jul 27 23:27:45…

数据库字段变更监控平台设计开发

序&#xff1a; 在开发过程中&#xff0c;在值班解决客服问题时&#xff0c;在分析定位别人写的业务代码问题时&#xff0c;重点是不是自己写的代码&#xff0c;只看到了数据库中落库最终数据&#xff0c;并不知道业务逻辑问题发生时数据库表中当时数据情况&#xff1f;如果能知…

【开源项目】低代码数据可视化开发平台go-view

数据可视化开发平台go-view 基本介绍 GoView 是一个Vue3搭建的低代码数据可视化开发平台&#xff0c;将图表或页面元素封装为基础组件&#xff0c;无需编写代码即可完成业务需求。 它的技术栈为&#xff1a;Vue3 TypeScript4 Vite2 NaiveUI ECharts5 Axios Pinia2 Plop…

NLP实验案例100个(6-10)

实验六 数据类型 一、实验目的及要求 熟悉数据的数据类型二、实验设备&#xff08;环境&#xff09;及要求 开发环境&#xff1a;jupyter notebook 开发语言以及相关的库&#xff1a;python开发语言 numpy库 三、实验内容与步骤 1.创建一个array类型的数据&#xff0c;设置…

【踩坑】三种方式解决 Homebrew failing to install - fatal: not in a git directory

问题描述 解决方法一 添加安全目录&#xff0c;没有测试。 git config --global --add safe.directory /opt/homebrew/Library/Taps/homebrew/homebrew- git config --global --add safe.directory /opt/homebrew/Library/Taps/homebrew/homebrew-cask 解决方法二 取消挂载这…

【python工具】html中表格转化为excel

背景 大家在实际的工作中可能会遇到这样的场景,查看某个统计的页面数据,其中一些数据是表格形式展示的,比如这是国家统计局关于人口统计的数据: 你想将表格内容下载下来根据自己的需要进行二次加工,但是页面没有提供下载功能或者需要你登陆才能下载。那么重点来了~~ 操…

Rabbitmq的安装与使用(Linux版)

目录 Rabbitmq安装 1.在Ubuntu上安装RabbitMQ&#xff1a; 打开终端&#xff0c;运行以下命令以更新软件包列表&#xff1a; 安装RabbitMQ&#xff1a; 安装完成后&#xff0c;RabbitMQ服务会自动启动。你可以使用以下命令来检查RabbitMQ服务状态&#xff1a; 2.在CentOS…

【前端知识】React 基础巩固(三十五)——ReduxToolKit (RTK)

React 基础巩固(三十五)——ReduxToolKit (RTK) 一、RTK介绍 Redux Tool Kit &#xff08;RTK&#xff09;是官方推荐的编写Redux逻辑的方法&#xff0c;旨在成为编写Redux逻辑的标准方式&#xff0c;从而解决上面提到的问题。 RTK的核心API主要有如下几个&#xff1a; confi…