【Linux初阶】fork进程创建 进程终止 进程等待

news2024/10/5 14:25:55

 🌟hello,各位读者大大们你们好呀🌟

🍭🍭系列专栏:【Linux初阶】

✒️✒️本篇内容:fork进程创建,理解fork返回值和常规用法,进程终止(退出码、退出场景、退出方法、exit),进程等待(wait、waitpid),阻塞等待和非阻塞等待

🚢🚢作者简介:本科在读,计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-

目录

一、进程创建

1.fork函数初识

2.fork返回值

(1)如何理解 fork函数有两个返回值

(2)如何理解 fork返回后,给父进程返回子进程的 pid,给子进程返回 0?

(3)如何理解同一个 id值,会返回两个不同的值,让 if 和 else if 同时执行

(4)理解写时拷贝

3.fork常规用法

4.fork调用失败的原因

二、进程终止

1.进程退出码

2.进程退出场景

3.进程常见退出方法

4.exit函数

5._exit函数

6.return

三.进程等待

1.wait方法

2.waitpid方法

3.获取子进程status

4.进程等待总结

四、再谈进程退出

五、阻塞等待 vs 非阻塞等待

1.阻塞等待应用示例 

2.非阻塞等待应用示例


一、进程创建

1.fork函数初识

在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

如下图所示,调用fork后,将生成一个新的进程

 代码示例如下

int main(void)
{
	pid_t pid;
	printf("Before: pid is %d\n", getpid());
	if ((pid = fork()) == -1)perror("fork()"), exit(1);
	printf("After:pid is %d, fork return %d\n", getpid(), pid);
	sleep(1);
	return 0;
}

运行结果:
[root@localhost linux]# . / a.out
Before : pid is 43676
After : pid is 43676, fork return 43677
After : pid is 43677, fork return 0

这里看到了三行运行结果,一行before,两行after。进程43676先打印before消息,然后它又打印after。另一个after消息是43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示

所以, fork之前父进程独立执行,fork之后,父子两个执行流分别执行
 

我们可以看到上述代码中并没有对父子进程进行条件限制,那么在程序运行起来时,在 fork之后,会先执行父进程还是子进程呢?实际上,fork之后,谁先执行完全由调度器决定。 

2.fork返回值

(1)如何理解 fork函数有两个返回值

首先,我们必须要知道 fork函数是操作系统为我们提供的,也就是说,fork操作是在操作系统内实现的

接下来,我们一起来看下 fork函数内部的结构,然后思考一个问题:在代码在 return之前,内部的核心代码实现完了吗?

答案是,实现完了!也就是说,子进程早在 return前就创建好了,并且可能已经在 OS的运行队列中,准备被调度了。对应的,当代码运行到 return时,会有父进程、子进程两个进程各自执行return

(2)如何理解 fork返回后,给父进程返回子进程的 pid,给子进程返回 0?

因为一个父亲的孩子可以有很多个,可是每个孩子都只有一个父亲。也就是说,孩子找父亲是具有唯一性的。以此类推,子进程 fork之后,不需要父进程的 id值,因为父进程具有唯一性。而父进程 fork之后需要对应子进程的 id,因为该父进程可能不止一个子进程,它需要对应的子进程 id做标识

(3)如何理解同一个 id值,会返回两个不同的值,让 if 和 else if 同时执行

返回的本质:就是写入。我们不知道父子进程谁先返回,谁先返回,谁就先写入 id值。由于进程具有独立性,进程在执行 fork相应代码时,会在操作系统内部进行写时拷贝,使 fork对应的进程可以返回两个不同的值,再让对应的父子进程根据自己返回的 id,去执行 if 或 else if 中的代码内容。

(4)理解写时拷贝

通常情况下,父子代码共享,父子进程在不写入(不修改共享部分的数据)时,对应的数据也是共享的。当任意一方试图写入,操作系统便会以写时拷贝的方式,给需要修改的一方在物理内存中开辟一块新空间,将原来的数据拷贝到新空间中,再对新空间中的数据做修改。具体见下图: 

———— 我是一条知识分割线 ————

3.fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

4.fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制(一个用户可以创建的进程是有限制的)


二、进程终止

1.进程退出码

这里给大家讲解一下进程退出码,我们知道写代码是为了完成某件事情,那我们如何知道这件事完成的怎么样呢?我们可以通过进程退出码来了解。

进程在退出时,会有对应的退出码,标定进程执行的结果是否正确

退出码的意义:0:success,!0:标识失败。 !0具体是几,标识不同的错误 -- 数字对人不友好,对计算机友好。

可以通过 echo $? 查看进程退出码

./mytest    #运行可执行程序mytest
echo $?    #永远记录了最近一个进程在命令行中执行完毕时对应的退出码(main->return ?;),这里的?为一个变量

———— 我是一条知识分割线 ————

一般而言,退出码,都必须有对应的文字描述,

  1. 可以自定义;
  2. 可以使用系统的映射关系(不太频繁)。

补充:在循环内定义变量,如果Linux系统版本比较低,需要在 makefile文件的依赖生成关系的行末加 -std=c99

strerror函数展现所有退出码 - 查看系统不同数字的映射关系

#include<string.h>

int main()
{
    for (int i = 0; i < 200; i++)
    {
        printf("%d: %s\n", i, strerror(i));
    }

    return 0;
}

2.进程退出场景

  • 代码运行完毕,结果正确,return 0;
  • 代码运行完毕,结果不正确,return !0;
  • 代码异常终止,退出码无意义。

3.进程常见退出方法

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

  • 1. 从main函数return返回;
  • 2. 任意地方调用exit(code);
  • 3. _exit。

异常退出:

  • ctrl + c,信号终止

exit - 库函数的一种,作用为终止一个进程的函数(头文件 #include<stdlib.h>),在调用 exit时,进程就终止退出了,代码不会继续向下执行。

_exit - 系统调用的一种,作用为终止一个进程的函数(头文件 #include<unistd.h>),在调用 _exit时,进程就终止退出了,代码不会继续向下执行。

我们尝试在Linux中编译下面的代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
	printf("hello");
	exit(0);
}
运行结果:
[root@localhost linux] # . / a.out
hello[root@localhost linux]#

int main()
{
	printf("hello");
	_exit(0);
}
运行结果:
[root@localhost linux] # . / a.out
[root@localhost linux]#

运行后我们会发现一个现象,exit(0)代码运行需要几秒后才能把 hello打印出来,这是因为数据在缓冲区中,进程退出后才能在缓冲区刷到我们对的屏幕上。但是当我们使用 _exit(0)时,频幕上不会有任何信息打印,也就是说,_exit()退出后不会刷新缓冲区。

总结:exit 终止进程,主动刷新缓冲区;_exit 终止进程,不会刷新缓冲区缓冲区存在于用户层

4.exit函数

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

参数:status 定义了进程的终止状态,父进程通过wait来获取该值,后面第三节会讲

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

exit最后也会调用 _exit, 但在调用 _exit之前,还做了其他工作:

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

5._exit函数

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

6.return

在我们编写 C/C++代码时,通常我们的 main函数结尾都会带有一段 return 0 的代码,不知道朋友们有没有想过这里的 return 0 的作用是什么呢?

结合我们上面的讲解,我们不难理解其实这里的 0就是进程退出码,如果我们不关心进程退出码,return 0就可以了。如果未来我们是要关心进程退出码的时候,要返回特定的数据表明不同的错误。


三.进程等待

我们之前就学习过僵尸进程的相关知识,我们知道在一个在进程退出的时候,需要退出的相关信息返回给父进程。那么假如子进程处于僵尸状态无法退出返回相关信息怎么办呢?这里就需要用到我们进程等待的知识了。我们可以通过进程等待的方式解决僵尸进程

进程等待的必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

1.wait方法

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

pid_t wait(int* status);

返回值:
成功返回被等待进程pid,失败返回 -1。
参数:
输出型参数,获取子进程退出状态, 不关心则可以设置成为NULL

常见使用方法

pid_t ret = wait(NULL);

代码示例

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

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        int cnt = 10;
        while (cnt)
        {
            printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(0); //进程退出
    }

    // 父进程
    sleep(15);
    pid_t ret = wait(NULL);
    if (id > 0)
    {
        printf("wait success: %d", ret);
    }

    sleep(5);
}

 

注意:这里我们可以把 wait理解为一个函数调用,目的是返回一个值(子进程的返回信息),给父进程

2.waitpid方法

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

常见使用方法

int status = 0; // 不是被整体使用的,有自己的位图结构
pid_t ret = waitpid(id, &status, 0); //wait子进程

补充: 

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

3.获取子进程status

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

退出状态中保存的是进程的退出码(判断运行结果是否正确),终止信号可以知道进程是否正常退出/结束。终止信号为0代表正常结束,只有终止信号为0才能去看退出状态,退出状态为0则代表运行结果正确,其他数字则代表其他不同的运行情况

  • 代码示例1(代码正常结束、运行正确,返回退出/终止状态信号0、exit的退出码10,这里的10是我自己自定义的)
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(10); //进程退出
    }

    // 父进程
    int status = 0; // 不是被整体使用的,有自己的位图结构
    pid_t ret = waitpid(id, &status, 0);
    if (id > 0)
    {
        printf("wait success: %d, sig number: %d, child exit code: %d\n", ret, (status & 0x7F), (status >> 8) & 0xFF);
    }

    sleep(5);
}

信号编号(终止信号)为低七位,我们可以通过 status & 0x7F获得;

退出码(在退出状态中)在次低八位,我们可以通过 (status>>8)&0xFF获得;

在程序正常运行的情况下,使用 exit返回,终止信号为0,退出码为 exit(?) 的 ?值。

  •  代码示例2(野指针,返回退出/终止状态信号11、exit的退出码0,这里的0是系统自动返回的)
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);
            sleep(1);
            int* p = NULL;//野指针
            *p = 100;
        }

        exit(10); //进程退出
    }

    // 父进程
    int status = 0; // 不是被整体使用的,有自己的位图结构
    pid_t ret = waitpid(id, &status, 0);
    if (id > 0)
    {
        printf("wait success: %d, sig number: %d, child exit code: %d\n", ret, (status & 0x7F), (status >> 8) & 0xFF);
    }

    sleep(5);
}

 在程序非正常运行时,使用 exit返回,终止信号为对应的信号值,退出码为0。终止信号不为0,代表非正常退出。

 11)SIGSEGV - 段错误,出现野指针后会有此类错误

  • 代码示例3(借助status值,检验子进程是否正常退出)

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

int main()
{
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        //child
        int cnt = 10;
        while (cnt)
        {
            printf("child running, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
            sleep(1);
            //    int *p = 0;
            //    *p = 100; //野指针问题
        }

        exit(10);
    }

    int status = 0;
    // 1. 让OS释放子进程的僵尸状态
    // 2. 获取子进程的退出结果
    // 在等待期间,子进程没有退出的时候,父进程只能阻塞等待
    int ret = waitpid(id, &status, 0);
    if (ret > 0)
    {
        // 是否正常退出
        if (WIFEXITED(status))
        {
            // 判断子进程运行结果是否ok
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
        else {
            //TODO
            printf("child exit not normal!\n");
        }
        //printf("wait success, exit code: %d, sig: %d\n", (status>>8)&0xFF, status & 0x7F);
    }

    return 0;
}

4.进程等待总结

wait和waitpid中的status参数,可以在子进程处于阻塞状态(运行完但是运行信息还没有被父进程回收)时,检测子进程的退出信息,将子进程存储于 PCB中的退出信息拿回到父进程中。这个操作由造作系统完成。


四、再谈进程退出

  1. 进程退出会变成僵尸,同时该进程也会把自己对应的退出码写入到自己的 task_struct中;
  2. wait/waitpid 是一个系统调用,也就是说,它们是由 OS完成的,OS有能力去读取子进程的task_struct;
  3. 所以,父进程获取到的子进程退出信息,是从退出子进程的 task_struct中获取到的

下面是 Linux源码 task_struct中的部分内容


五、阻塞等待 vs 非阻塞等待

  • 阻塞等待:持续检测子进程状态,不进行其他工作;
  • 非阻塞等待:间隔性检测子进程状态,如果没有就绪,直接返回。在子进程没有退出的前提下,每一次的退出都是以此非阻塞等待;
  • 非阻塞等待下,获取子进程退出状态成功和不成功,对应的返回值不同成功返回一个大于0的值,不成功则返回0
  • 多次非阻塞等待称为轮询

非阻塞等待有什么好处?

不会让等待占用父进程所有的精力,可以在轮询期间,干干别的!具体是什么意思呢?简单来说,就是父进程每隔一段时间去查看等待是否成功,在此期间,父进程可以进行别的工作

1.阻塞等待应用示例 

int main()
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		printf("%s fork error\n", __FUNCTION__);
		return 1;
	}
	else if (pid == 0) 
	{ 
		//child
		printf("child is run, pid is : %d\n", getpid());
		sleep(5);
		exit(257);
	}
	else 
	{
		int status = 0;
		pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
		printf("this is test for wait\n");
		if (WIFEXITED(status) && ret == pid) {
			printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
		}
		else {
			printf("wait child failed, return.\n");
			return 1;
		}
	}
	return 0;
}

运行结果:
[root@localhost linux] # . / a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is : 1.

 可以观察到,进程在打印 child is run, pid is : 45110  5s之后才打印后续的代码

2.非阻塞等待应用示例 

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

#define NUM 10

typedef void (*func_t)(); //函数指针

func_t handlerTask[NUM]; //函数指针数组

//样例任务
void task1()
{
    printf("handler task1\n");
}
void task2()
{
    printf("handler task1\n");
}
void task3()
{
    printf("handler task1\n");
}

void loadTask()
{
    memset(handlerTask, 0, sizeof(handlerTask));//将数组handlerTask初始化为0,需要#include <string.h>
    handlerTask[0] = task1;//将3个函数指针对应3个任务
    handlerTask[1] = task1;
    handlerTask[2] = task1;
}

void addtask()
{}

int main()
{
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        //child
        int cnt = 10;
        while (cnt)
        {
            printf("child running, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(10);
    }

    loadTask(); //给父进程装载一批任务
    // parent
    int status = 0;
    while (1)
    {
        pid_t ret = waitpid(id, &status, WNOHANG); //WNOHANG: 非阻塞-> 子进程没有退出, 父进程检测时候,立即返回
        if (ret == 0)
        {
            // waitpid调用成功 && 子进程没退出
            //子进程没有退出,我的waitpid没有等待失败,仅仅是监测到了子进程没退出.
            printf("wait done, but child is running...., parent running other things\n");
            for (int i = 0; handlerTask[i] != NULL; i++)
            {
                handlerTask[i](); //采用回调的方式,执行我们想让父进程在空闲的时候做的事情
            }
        }
        else if (ret > 0)
        {
            // 1.waitpid调用成功 && 子进程退出了
            printf("wait success, exit code: %d, sig: %d\n", (status >> 8) & 0xFF, status & 0x7F);
            break;
        }
        else
        {
            // waitpid调用失败
            printf("waitpid call failed\n");
            //    break;
        }
        sleep(1);
    }

    return 0;
}

可以观察到,在子进程还没有退出的时候,父进程完成了其他的工作 


 🌹🌹 Linux下的fork进程创建 & 进程终止 & 进程等待 的知识大概就讲到这里啦,博主后续会继续更新更多Linux操作系统的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪 

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

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

相关文章

第08章_聚合函数

第08章_聚合函数 我们上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类&#xff0c;叫做聚合&#xff08;或聚集、分组&#xff09;函数&#xff0c;它是对一组数据进行汇总的函数&#xff0c;输入的是一组数据的集合&#xff0c;输出的是单个值。 1. 聚合函数介绍 什么是…

【sentinel】Sentinel工作主流程以流控规则源码分析

Sentinel工作主流程 在Sentinel里面&#xff0c;所有的资源都对应一个资源名称&#xff08;resourceName&#xff09;&#xff0c;每次资源调用都会创建一个Entry对象。Entry可以通过对主流框架的适配自动创建&#xff0c;也可以通过注解的方式或调用SphU API显式创建。Entry创…

跨境seo引流的13种方法

跨境SEO引流是一种通过搜索引擎优化来吸引国际目标受众并增加网站流量的策略。以下是一些跨境SEO引流的关键步骤和技巧&#xff1a; 目标受众研究&#xff1a;了解你的目标市场和受众群体。了解他们的需求、喜好、购买习惯以及使用的搜索引擎和关键词。这将帮助你确定你的跨境S…

chatgpt赋能Python-python3怎么合并列表

Python3&#xff1a;合并列表的不同方法 如果你正在使用Python 3&#xff0c;那么你很可能会面对合并列表的问题。合并列表&#xff08;也称为连接列表或串联列表&#xff09;是将两个或多个列表组合成一个列表的过程&#xff0c;这是在编程中很常见的任务。在这篇文章里&…

Python并发编程:异步编程和多线程技术的应用和效率优化

第一章&#xff1a;介绍 在当今的软件开发领域&#xff0c;高效的并发编程对于处理大规模数据和提升系统性能至关重要。Python作为一种简洁、易读且功能强大的编程语言&#xff0c;提供了多种并发编程的技术和工具。本文将深入探讨Python中的异步编程和多线程技术&#xff0c;…

chatgpt赋能Python-python3如何画图

Python3如何画图&#xff1f; Python是一种高级编程语言&#xff0c;它有着多种用途&#xff0c;包括数据分析和可视化。Python3是Python的最新版本&#xff0c;它具有更好的性能和易用性。在这篇文章中&#xff0c;我们将介绍如何使用Python3来画图&#xff0c;并探讨其优势和…

KingbaseES 逻辑读与物理读

oracle数据库中逻辑读&#xff0c;物理读 数据访问方式&#xff1a;数据库少不了和操作系统进行数据交互&#xff0c;表数据最好的方式是从数据库共享池中访问到&#xff0c;避免发生磁盘IO&#xff0c;当然如果共享池中没有访问到数据就难免发生磁盘IO。 物理读&#xff1a;从…

第三篇、Arduino uno、nano、2560用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向

0、结果 说明&#xff1a;先来看看拍摄的显示结果&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;本次使用的oled是0.96寸的&#xff0c;别的规格的屏幕不一定适用本教程&#xff0c;一般而言有显示白色、蓝色和蓝黄一起显示的&#xff0…

【小沐学Web】Node实现Web图表功能(ECharts.js,React)

&#x1f388;&#x1f388;&#x1f388;Python实现Web图表功能系列&#xff1a;&#x1f388;&#x1f388;&#x1f388;1&#x1f388;【Web开发】Python实现Web图表功能&#xff08;D-Tale入门&#xff09;&#x1f388;2&#x1f388;【Web开发】Python实现Web图表功能&a…

Fragment 要你何用?2.0版本

前言 在之前的文章里有分析过Activity、Fragment、View之间的关联&#xff0c;也简单分析了Fragment的原理。 本篇将对Fragment被高频使用的场景以及一些坑点作分析&#xff0c;通过本篇文章&#xff0c;你将了解到&#xff1a; 老生常谈&#xff1a;为什么需要Fragment?Frag…

Java 创建一个大文件

有时候&#xff0c;我们在对文件进行测试的时候&#xff0c;可能需要创建一个临时的大文件。 那么问题来了&#xff0c;在 Java 中如何创建大文件呢&#xff1f; 问题和解决 有些人想到的办法就是定义一个随机的字符串&#xff0c;然后重复很多次&#xff0c;然后将这个字符…

第一篇:强化学习基本原理通俗介绍

你好&#xff0c;我是zhenguo&#xff08;郭震&#xff09; 今天强化学习第一篇&#xff1a;白话介绍强化学习的基本原理 强化学习是一种机器学习方法&#xff0c;旨在让智能体&#xff08;agent&#xff09;通过与环境的交互学习如何做出最优的行动选择以获得最大的累积奖励。…

Rust每日一练(Leetday0004) 正则表达、盛水容器、转罗马数字

目录 10. 正则表达式匹配 Regular Expression Matching &#x1f31f;&#x1f31f;&#x1f31f; 11. 盛最多水的容器 Container with most water &#x1f31f;&#x1f31f; 12. 整数转罗马数字 Integer to Roman &#x1f31f;&#x1f31f; &#x1f31f; 每日一练…

new和delete用法详解

本篇文章对C中的new和delete进行详解。在讲解new和delete时&#xff0c;我们会对比C语言中的malloc和free&#xff0c;看看两者的区别和相似之点。希望本篇文章会对你有所帮助。 文章目录 一、什么是new和delete 二、new和delete的用法 2、1 new和delete操作内置类型 2、2 new和…

中青宝两大议案被否!散户又“赢了”?

21.93万股&#xff0c;就能决定股东大会上的议案成败——离奇的一幕在中青宝上演。 5月18日&#xff0c;中青宝召开2022年度股东大会。会上&#xff0c;《关于2023年度日常关联交易预计的议案》《关于非独立董事2023年度薪酬方案的议案》两项议案被否。 此次股东大会上&#x…

linux设置静态ip与windows互相ping通、设置静态ip之后不能联网和网络服务重启失败的问题

1.虚拟机linux设置静态ip与windows互相ping通及设置静态ip之后不能联网问题一站式解决&#xff1a; 转载&#xff1a;https://www.codenong.com/cs105332412/ 2.遇到网络服务重启失败的问题 按照提示查看网络服务的状态 看到这篇博文https://www.cyberithub.com/failed-to-s…

Ae 效果详解:Keylight(1.2)

Ae菜单&#xff1a;效果/Keying/Keylight(1.2) Effects/Keying/Keylight(1.2) Keylight 是一款工业级的蓝幕或绿幕键控器&#xff0c;核心算法由 Computer Film 公司开发&#xff0c;并由 The Foundry 公司进一步开发移植到 Ae。 Keylight 在制作专业品质的抠像效果方面表现出色…

第11章_数据处理之增删改

第11章_数据处理之增删改 1. 插入数据 1.1 实际问题 解决方式&#xff1a;使用 INSERT 语句向表中插入数据。 1.2 方式1&#xff1a;VALUES的方式添加 使用这种语法一次只能向表中插入一条数据。 情况1&#xff1a;为表的所有字段按默认顺序插入数据 INSERT INTO 表名 VAL…

Python 学习 2022.08.28 周日

文章目录 一、 概述1.1&#xff09; 之前写的文章&#xff1a;1.2) 基础点1.3) 配置1.4) Python2 和 Python3 的区别1.5&#xff09; 相关问题跟踪解决1.6) 其他 一、 概述 1.1&#xff09; 之前写的文章&#xff1a; 【Python大系】Python快速教程《Python 数据库 GUI CGI编…

clion开发stm32之flash驱动(f4系列)

前言 使用的开发工具(clionmsys2openocd)使用的开发版芯片stm32f407vet6参考手册为stm32f4中文参考文档 查看中文手册 ## 驱动代码 头文件(bsp_flash.h) #ifndef STM32F103VET6_PROJECT_BSP_FLASH_H #define STM32F103VET6_PROJECT_BSP_FLASH_H #include "sys.h"…