【Linux系列】掌控 Linux 的脉搏:深入理解进程控制

【Linux系列】掌控 Linux 的脉搏:深入理解进程控制
在这里插入图片描述



🫧 励志不掉头发的内向程序员个人主页
 ✨️ 个人专栏: 《C++语言》《Linux学习》

🌅偶尔悲伤,偶尔被幸福所完善


👓️博主简介:

在这里插入图片描述


文章目录


前言

我们之前就学习了如何去创建一个进程,这也是我们控制进程的一部分,本章节我们就接着创建进程来讲讲如何终止进程以及我们怎么处理僵尸进程等问题,我们一起来看看吧。
在这里插入图片描述

一、进程创建

进程创建就是 fork 函数的使用,我们在前面章节就已经了解且讲解过了,所以此处就不过多赘述了。

二、进程终止

2.1、进程退出场景

  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果不正确。
  • 代码异常终止。

我们的子进程也是进程,由父进程创建,我们的父进程创建子进程的目的肯定是为了达成某种目的的。所以我们的父进程就应该知道子进程的运行结果来判断子进程是怎么终止的。所以我们的进程终止就是要给父进程一个交代。

2.2、进程常见退出方法

我们的退出方法有很多。

首先就是我们的 main 函数结束,就表示进程的结束,其他的函数结束只表示自己的函数调用完成。

其次我们可以调用我们的 exit 接口。

(1)退出码

我们的 main 函数作为我们的程序的入口,它所返回的 0 或者非 0 肯定就是代表我们程序的执行情况的。由于我们的程序运行结束时有三种场景,而我们的返回值通常就是表达了我们的前两种情况,只有我们的 main 函数运行完毕并且结果正确,才会返回我们的 0,如果我们的结果不对,但是运行完毕就会返回我们的非零。 既然是非零了,那我们就能给不同的数字赋予不同的含义去返回。

我们去创建一个进程,可能是有返回的内容,比如 printf 给我们看到结果,但是我们也有进程是方面都不展示的,我们怎么知道它的返回值呢?我们的子进程的返回值其实是返回给我们的父进程的,如果我们要去查看我们的进程的返回数字,就只要输入 echo $? 就可以查出。

在这里插入图片描述


我们运行了一个进程,它正常的退出了,所以返回值是 0。

所以说我们的子进程退出了,我们的父进程 bash 想要知道我们的子进程的执行情况,就要获得我们子进程所对应的退出码。而我们的 echo $? 的指令就表示我们打印最近一个进程退出时的退出码。main 函数的返回值也就叫做进程退出码。我们的进程退出码是要在退出时写到我们的 task_struct 内部的。

我们来试着查看一下我们的退出码试试。

#include<stdio.h>#include<string.h>intmain(){int i =0;for(; i <200; i++){printf("%d->%s\n", i,strerror(i));}return0;}

我们通过运行上面的代码就可以试着查看我们的退出码。

在这里插入图片描述


我们可以看到我们的退出码是有很多的,一个是有 134 个退出码。

但是如果是异常该如何返回呢?

#include<stdio.h>intmain(){int a =10; a /=0;return89;}

这个代码就是异常的,我们看看如果它运行到 return,就会返回 89,我们运行试试看。

在这里插入图片描述


我们可以看到它的结果是 136 而不是 89。这里要说的是,如果我们的代码异常了,那我们的退出码就无意义了。而我们的进程一旦异常了,异常就是我们的进程收到的信号。

(2)exit 函数

这个函数的主要作用就是引起一个进程终止。

在这里插入图片描述


要给它传输一个 statue(状态),也就是进程退出时的退出码。在代码中调用 statue,它的作用等价于 return。

我们来试着用用 exit。

#include<stdlib.h>#include<stdio.h>intmain(){exit(23);}
在这里插入图片描述
#include<stdio.h>#include<stdlib.h>voidfun(){printf("fun begin!\n");exit(40);printf("fun end!\n");}intmain(){fun();printf("main!\n");return0;}

运行后我们发现,它只打印了一句话,后面的却不打印了。

在这里插入图片描述

所以说我们在任何地方调用 exit 都表示进程结束。所以进程就直接退出了。

(3)_exit 函数

在这里插入图片描述


这个和 exit 的区别在于谁调用 _exit,_exit 就终止谁。它的头文件是 unistd。

#include<stdio.h>#include<unistd.h>voidfun(){printf("fun begin!\n");_exit(4);printf("fun end!\n");}intmain(){fun();printf("main!\n");return0;}
在这里插入图片描述


我们可以发现它也是只跑了一半,退出码是 4。

我们的 exit 和 _exit,一个是 C 语言提供的,一个是系统提供的。

// 1、#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){printf("main!");sleep(2);exit(23);}// 2、#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){printf("main!");sleep(2);_exit(23);}

我们来运行一下我们的 1 和 2。

在这里插入图片描述


在这里插入图片描述

我们可以发现,我们的 1 在经过两秒的等待后系统就把我们的数据从缓冲区刷新到我们的显示器上,但是我们的 2 却什么都没有。

所以说它们的区别就在于进程调用 exit 退出的时候,会进行缓冲区的刷新。但是如果是 _exit,就不会进行缓冲区的刷新。

我们的 exit 是库函数提供的,而我们的 _exit 是系统调用提供的,我们能够终止进程的就只有 OS,所以说我们的 exit 内部一定封装了我们的 _exit。由此观之,我们的缓冲区一定不是操作系统内部的缓冲区。它其实是库级别的缓冲区。

(4)return 退出

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

三、进程等待

3.1、进程等待必要性

之前讲过了,我们的子进程退出时,如果我们的父进程不管,就会造成子进程一直处于僵尸进程的问题,从而导致内存泄漏。这个状态下,我们的进程就刀枪不入了。而且我们的父进程也有必要知道我们的子进程任务完成的如何。所以我们的父进程就得通过进程等待的方式回收子进程的资源(必须的)和获取子进程退出信息(可选的)。

3.2、进程等待方法

(1)wait 方法

#include<stdio.h>#include<unistd.h>#include<stdlib.h>intmain(){pid_t id =fork();if(id ==0){int cnt =5;while(cnt){printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());sleep(1); cnt--;}exit(0);//子进程退出}// 父进程sleep(100);return0;}

我们可以看到,我们的子进程在运行结束后一直处于僵尸状态。

在这里插入图片描述

我们这个时候来试着把僵尸状态解决掉,我们用 wait 接口来解决。

在这里插入图片描述

我们的 wait 函数只有一个参数 *status,输出型参数,需要我们传一个整型变量的地址,调用成功这个参数后,会把子进程的退出信息放到 status 中,然后给上层(父进程)拿到。

我们的 wait 是等待任意个退出的子进程。如果我们的进程创建了 1 个子进程,那我们的 wait 就等待一个子进程的退出,如果是 10 个,wait 就会阻塞,直到我们的 10 个中的任意一个退出了,他就把我们退出的进程返回。

wait 的成功返回后,返回值是成功返回的子进程的 pid,也就是目标 Z 进程的 pid,如果失败了,就返回 -1。

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){pid_t id =fork();if(id ==0){int cnt =5;while(cnt){printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());sleep(1); cnt--;}exit(0);}sleep(10);pid_t rid =wait(NULL);if(rid >0){printf("wait success, rid: %d\n", rid);}sleep(10);return0;}

运行后。

在这里插入图片描述

我们可以看到,在子进程运行完但是 wait 还没运行时是僵尸进程,但是 wait 运行后子进程就退出了。

(2)waitpid 方法

我们如果想要更多的控制,我们可以使用 waitpid,它比 wait 更高级。

在这里插入图片描述


返回值和 wait 的返回值一样。这个 options 参数是进行阻塞控制的。和 status 一起后面细谈。这个 pid 参数是指要等待什么子进程,就把它的 pid 传给 waitpid。

在这里插入图片描述

如果传入 -1 给 pid,就相当于 wait。0 和小于 -1 暂时不考虑。

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){pid_t id =fork();if(id ==0){int cnt =5;while(cnt){printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());sleep(1); cnt--;}exit(0);}sleep(10);pid_t rid =waitpid(-1,NULL,0);if(rid >0){printf("wait success, rid: %d\n", rid);}sleep(10);return0;}

这一串代码等价于 wait 的代码。

当然,我们也可以把 -1 改成任意子进程的 pid,这样就实现的定向等待指定子进程的功能。

(3)获得子进程 status

我们的父进程要通过我们的 status 来获取子进程返回的数据从而得到退出信息。从而知道我们的子进程把任务完成的怎么样,也就是我们的进程退出码,即 main 返回值来判断退出结果是否正确。

我们试着使用一下 status。

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>#include<errno.h>intmain(){pid_t id =fork();if(id ==0){int cnt =3;while(cnt){printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());sleep(1); cnt--;}exit(1);}int status =0;pid_t rid =waitpid(id,&status,0);if(rid >0){printf("wait success, rid: %d, status: %d\n", rid, status);}else{printf("wait failed: %d: %s\n", errno,strerror(errno));}return0;}

我们运行后可以看到。

在这里插入图片描述

但是不是 exit(1) 吗,为什么这里显示 256 了呢?实际上我们的 status 不是我们想想的那样直接拿到我们整数的退出码。

在这里插入图片描述

实际上,我们的 status 看上去是一个普通的整数,实际上这个 status 是被划分成若干区域的,可以把它看成一个位图。共 32 个 bit 位,我们的高 16 位不考虑,而低 16 位中,我们的 8 ~ 15 才是表示我们的退出码,所以说我们的 exit(1) 中的 1 就是表示 0000000100000000 也就是 256 了。而后面的两部分,我们到信号时再谈。所以我们想要获取整型变量,我们得将我们的 status 右移 8 位,再提取我们中间的 8 位,就可以拿到我们的 1 了。

(status >>8)&0xff;

这样就可以获取我们的退出码了。

在这里插入图片描述


但是我们还有一种退出叫做异常退出是什么呢?其实我们的异常涉及到我们的信号,这里就不多涉及。

但是我们的 waitpid 和 wait 是从哪里拿的退出信息呢?我们在之前就明白了我们的子进程在进入僵尸进程时会释放内存,页表代码和数据等,所以我们就能够猜到其实退出信息存储在我们的 PCB 中,事实也的确如此。

在这里插入图片描述


我们的 waitpid 通过我们的系统调用提供的接口获取到了我们的子进程的信息后,在交给我们父进程的地址空间上,我们的 getpid() 和 getppid() 也是类似的原理。

我们刚才提取我们的退出码是用位操作,但是我们计算机不希望我们这么做,所以提供了若干个宏来进行提取,一个是 WEXITSTATUS(status),其实就是用宏的方式封装了一下我们的位操作。还有一个是 WIFEXITED(status),表示我们的子进程是不是正常退出,就是检测我们的信号是不是为真。

(4)阻塞与非阻塞等待

在我们子进程等待的时候还有一个参数就是我们的 options。

在这里插入图片描述

我们的 options 可以设置一些选项,当我们的默认为 0 时就是阻塞等待。

还有一个是 WNOHANG,它的作用是如果我们的子进程没有退出的话,我们就立即返回。我们把这种特性称作非阻塞调用。我们 WNOHANG 中的 W 就是 wait 的意思,HANG 是指,当我们计算机中,如果卡死了,就称为 HANG(夯)住了。

我们这里来聊聊什么是非阻塞。

我们假如要去找一个人,到了他家楼下,我们打电话给他去询问他下来了没有,他告诉我们他还没有下来,我们就把电话挂了,等一会儿再打电话给他询问,如果还没有那就再挂了,一直重复直到他下来了,这就是非阻塞调用,在挂断电话后和再次打电话之间我们可以做一些自己的事情,比如玩玩手机,刷刷视频等(这是非阻塞调用的好处)。我们的打电话询问又挂电话的行为称之为非阻塞轮询。我们就是父进程,他就是子进程,打电话就是一次调用,这就是非阻塞。在这种情况中,我们一般是用到循环去轮询,这个时候我们的返回值就可以判断结束了没有,如果大于 0 就说明等待结束。等于 0 说明我们的 waitpid 调用结束,但是子进程没有退出。小于 0 说明等待失败。

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>intmain(){pid_t id =fork();if(id ==0){int cnt =3;while(1){sleep(3);printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());sleep(1); cnt--;}exit(10);}while(1){int status =0;pid_t rid =waitpid(id,&status, WNOHANG);if(rid >0){printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid,(status >>8)&0xff, status &0xff);break;}elseif(rid ==0){printf("本轮调用结束, 子进程没有退出\n");sleep(1);}else{printf("等待失败\n");break;}}}

我们试着运行可以看到。

在这里插入图片描述

我们的父进程一直在询问我们的子进程结束没有。
当我们试着kill掉这个子进程时可以看到。

在这里插入图片描述

我们等待就成功了。

而阻塞就是我们打电话去问他下来没,得知没有下来后不挂电话,一直打着,直到下来为止。在下来期间,我们一直阻塞在这里。

四、进程程序替换

4.1、替换原理

在这里插入图片描述


进程替换的原理就是通过系统提供的 exec 系列接口,将想要的代码和数据与当前进程的代码和数据进行替换。在程序替换的过程中,并没有创建新的进程,只是把当前进程的代码和数据用新的程序的代码和数据覆盖式的进行替换!我们的替换是可以替换一切可以转化成进程的程序,而不单单是一些命令。

#include<stdio.h>#include<unistd.h>intmain(){printf("我的程序运行了!\n");execl("/usr/bin/ls","ls","-l","-a",NULL);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述


我们可以看到我们的程序没有运行到我们的程序运行完毕那里,说明我们的程序一旦替换成功了,就去执行新代码了,原始代码的后半部分已经不存在了。

如果 exec 函数替换失败时,就会执行后半部分代码,并且返回 -1。如果成功了就不会有返回值,因为会给替换掉。所以 exec 函数不用对返回值做判断,一旦返回就是失败。

4.2、替换函数

(1)函数解释

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

这个函数的第一个参数要以路径 + 程序名的方式写入,作用就是进程要执行谁。

第二个参数我们在命令行怎么填,我们就怎么填第二个参数。而结尾必须以 NULL 结尾,表明参数传递完成。

在这里插入图片描述

第二个参数就是表示进程怎么执行它。我们像这样类似于链表的方式将参数一个一个的传入,所以 execl 的 l 就是 list 的意思。

如果我们在 exec 系列接口运行时,会把我们当前进程的内容全部覆盖掉,但是我们不希望这么做,这个时候就可以创建一个子进程,让我们的子进程去完成 exec 的工作而不影响我们的父进程的工作。原因是子进程的代码和数据在改变时发生了写实拷贝,使得子进程和父进程完全区分开来了。

  1. int execlp(const char *file, const char *arg, …);

我们的 execlp 中的 p 就是相当于我们环境变量中的 PATH 的意思。说明我们要执行的目标程序不需要告诉我们路径,只需要告诉我们要执行的文件名就行了。因为 execlp 会自动在环境变量 PATH 中查找指定的命令。我们第二个参数的传递方式同 execl。

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);execlp("ls","ls","-l","-a",NULL);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述
  1. int execv(const char *path, char *const envp[]);

我们的这个 exec 没有带 p,所以我们的第一个参数是同 execl 的,要告诉我们要执行谁,所以要把我们的路径带上,要么是绝对,要么是相对的。我们的 execv 的 v 其实可以理解为 vector,所以第二个参数就是以数组的形式呈现。如果我们要用 execv 执行某个命令,我们就得给它提供一个命令行参数表,也就是一个指针数组。

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"ls","-l","-a",NULL};execv("/usr/bin/ls", argv);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述

所以说 v 和 l 只是以不同形式呈现罢了。

  1. int execvp(const char *file, char *const argv[]);

这个接口的两个参数在前面都有出现。

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"ls","-l","-a",NULL};execvp(argv[0], argv);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述
  1. int execvpe(const char *file, char *const argv[], char *const envp[]);

p 表示不用带变量路径,v 表示传入一个命令行参数表,而我们的 e,也就是第三个参数则是表示我们的环境变量。
我们先创建一个 other.cc 程序。

#include<iostream>#include<cstdio>#include<unistd.h>intmain(int argc,char*argv[],char*env[]){ std::cout <<"hello C++, My Pid Is: "<<getpid()<< std::endl;for(int i =0; i < argc; i++){printf("argv[%d]: %s\n", i, argv[i]);}printf("\n");for(int i =0; env[i]; i++){printf("env[%d]: %s\n", i, env[i]);}return0;}

运行后。

在这里插入图片描述

这个时候我们可以让我们的 other 替换掉 proc.c 的子进程。

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"other","-a","-b","-c","-d",NULL};char*const env[]={"MYVAL=123456789",NULL};execvpe("./other", argv, env);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述

我们可以发现,我们的环境变量就只剩下我们导入的一个环境变量了。所以我们的这个环境变量指的是替换的子进程,使用全新的 env 列表。如果我们想要以新增的方式增加环境变量怎么办呢?

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"other","-a","-b","-c","-d",NULL};char*const env[]={"MYVAL=123456789",NULL};execvp("./other", argv);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

如果我们直接使用 execvp,运行后。

在这里插入图片描述

我们发现我们的子进程是可以获得我们的环境变量。其实我们的 exec 内部默认自己传了。

我们如果想要在原基础上增加我们的 env,可以使用 putenv 函数。

在这里插入图片描述

哪个进程调它,就在哪个进程增加一个环境变量。这个进程导入新的环境变量后,它的父进程是看不到的,它的子进程才能看到。

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>char*newenv ="MYVAL=123456789";//putenv必须这样新增envintmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"other","-a","-b","-c","-d",NULL};char*const env[]={"MYVAL=123456789",NULL};putenv(newenv);execvp("./other", argv);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述

所以其实我们想要带入环境变量没有那么复杂,我们带 e 的接口是覆盖式的加入,直接使用 putenv 即可。但是如果硬是要使用 execvpe 呢?

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>char*const addenv[]={"MYVAL=123456789",NULL};intmain(){printf("我的程序运行了!\n");if(fork()==0){printf("I Am Child, My Pid Is: %d\n",getpid());sleep(1);char*const argv[]={"other","-a","-b","-c","-d",NULL};for(int i =0; addenv[i]; i++){putenv(addenv[i]);}externchar**environ;//声明execvpe("./other", argv, environ);exit(1);}waitpid(-1,NULL,0);printf("我的程序运行完毕了\n");return0;}

运行后。

在这里插入图片描述
  1. int execle(const char *path, const char *arg, …, char *const envp[]);

同上。

  1. execve(const char *filename, char *const argv[], char *const envp[]);

这个没有和前面的 6 个放在一块,它是在 2 号手册中的,但是前 6 个是在 3 号手册。也就是这个是系统调用。前 6 个是系统调用的语言层面的封装。前 6 个的底层都是这个。这也就是为什么我们不传环境变量也能获取,因为 execve 已经传了。如果传了就用传的,没有就用默认的。这也就是为什么传了就用新的环境变量替代了。

(2)命名解释

exec 有 l,p,e,v 四个后缀:

  • l 表示 list,指我们传参数像链表一样一个一个的传。
  • p 表示 PATH,就是说我们不用指明我们的路径,只需要说明要做什么即可。
  • e 表示 env,就是环境变量。
  • v 表示 vector,就是指我们在指明做什么时可以用一个命令行参数表指明。


总结

这就是我们进程控制的主要内容了,我们了解完如何控制进程,我们会发现我们现在可以试着实现一个自定义的 shell 了。我们下一章节便来讲解原理,我们下一章再见。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=fh78zroe8u8

🎇坚持到这里已经很厉害啦,辛苦啦🎇ʕ • ᴥ • ʔづ♡ど

Read more

【大数据存储与管理】分布式文件系统HDFS:03 HDFS的相关概念

【大数据存储与管理】分布式文件系统HDFS:03 HDFS的相关概念

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈大数据技术原理与应用 ⌋ ⌋ ⌋专栏系统介绍大数据的相关知识,分为大数据基础篇、大数据存储与管理篇、大数据处理与分析篇、大数据应用篇。内容包含大数据概述、大数据处理架构Hadoop、分布式文件系统HDFS、分布式数据库HBase、NoSQL数据库、云数据库、MapReduce、Hadoop再探讨、数据仓库Hive、Spark、流计算、Flink、图计算、数据可视化,以及大数据在互联网领域、生物医学领域的应用和大数据的其他应用。 【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/BigData_principle_application。 文章目录 * 一、块 * 二、名称节点和数据节点 * 三、第二名称节点 * 小结 本文介绍 HDFS 中的相关概念,包括块、名称节点和数据节点、第二名称节点。

By Ne0inhk
03.Python IDE / 编辑器选型指南:PyCharm/VS Code/IDLE 使用对比

03.Python IDE / 编辑器选型指南:PyCharm/VS Code/IDLE 使用对比

目录 * 前言 * 一、主流 Python IDE / 编辑器介绍:不同 “工作台” 的特点 * 1.1 IDLE:Python 自带的 “简易小书桌” * 生活化类比 * 核心特点 * 界面直观 * 1.2 VS Code:轻量可定制的 “多功能折叠桌” * 生活化类比 * 核心特点 * 界面直观 * 1.3 PyCharm:专业的 “Python 专用办公桌” * 生活化类比 * 核心特点 * 界面直观 * 二、不同场景下的 IDE / 编辑器选型建议:选对工具少走弯路 * 三、主推 PyCharm:下载与安装(Windows 版,新手友好) * 3.

By Ne0inhk

Flutter 三方库 in_date_utils 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、高效的日期逻辑处理与万年历算法引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 in_date_utils 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、高效的日期逻辑处理与万年历算法引擎 在鸿蒙(OpenHarmony)系统的日历、任务管理或考勤应用中,如何快速计算某月的天数、判断闰年、或优雅地对日期进行加减操作?in_date_utils 为开发者提供了一套开箱即用的日期增强工具集。本文将深入实战其在鸿蒙生态中的应用。 前言 什么是 in_date_utils?它是 Dart 原生 DateTime 的强力补丁。在 Flutter for OpenHarmony 的实际开发中,我们经常需要处理诸如“上周一的日期”、“本月最后一个周五”等复杂的业务逻辑。利用该库,我们可以避免重复编写琐碎的日期数学运算,让鸿蒙应用的代码更加简洁、易读且稳健。 一、

By Ne0inhk
[python]-多任务

[python]-多任务

介绍 多任务的优势 多个任务同时执行能够充分利用CPU资源,大大提高程序执行效率 1. 思考一下: 利用现学知识能够让多个任务同时执行吗? 不能,因为之前所写的程序都是单任务的,也就是说一个函数或者方法执行完成,另外一个函数或者方法才能执行,要想实现多个任务同时执行就需要使用多任务。 概念 多任务是指在同一时间内执行多个任务(给我们的感觉)。 1. 例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。 1. 多任务的两种表现形式 * 并发: 在一段时间内,交替执行任务 * 并行: 在一段时间内,真正的同时一起执行多个任务 进程 进程的概念 进程(Process)是CPU资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位 通俗理解: 一个正在运行的程序就是一个进程. 例如: 正在运行的qq,微信等他们都是一个进程 注意: 一个程序运行后至少有一个进程 多进程的作用 图中是一个非常简单的程序, 1. 一旦运行hello.py这个程序,按照代码的执行顺序, 2. func_a函数执行完

By Ne0inhk