『 Linux 』使用fork函数创建进程与进程状态的查看

文章目录

    • 🖥️ 前言 🖥️
    • 🖥️ 通过系统调用获取进程标识符 🖥️
      • 💻 进程标识符PID
      • 💻 父进程标识符PPID
    • 🖥️ 通过系统调用创建子进程 fork() 🖥️
      • 💻 那么为什么在fork()函数当中有两个返回值;
        • 🔋 创建子进程后分区工作 🔋
    • 🖥️ 进程状态 🖥️
      • 💻 操作系统的进程状态
      • 💻 Linux操作系统下的进程状态
        • 🔋 R 运行状态(running) 🔋
        • 🔋 S 睡眠状态 (sleeping) 🔋
        • 🔋 D 磁盘休眠状态(Disk sleep) 🔋
        • 🔋 T(t) 停止、暂停状态(tracing stopped) 🔋
        • 🔋 T 停止、暂停状态(stopped) 🔋
        • 🔋 X 死亡状态(dead) 🔋
        • 🔋 前台进程 🔋


🖥️ 前言 🖥️

在『 Linux 』进程概念 中了解到了操作系统对于进程的管理也是使用了"先描述后组织的方式",且提到可以使用以下方式来查看进程:

  • ps

    只能查看当前渠道内的进程,若是需要查看所有进程应该加上修饰,即使用ps axj | grep id_PID来查看进程;

  • top

    top命令更像是Windows下的任务管理器,一般用来观察内存当中占用较大的那个进程;

  • ls /proc

    使用ls命令以目录的形式来查看进程;


🖥️ 通过系统调用获取进程标识符 🖥️

💻 进程标识符PID

在Linux中不仅可以使用以上方式查看进程,也可以通过系统调用的方式使程序获取到当前进程标识符;

在Linux中有一个头文件为<unistd.h>这个头文件提供了一些关于系统调用和底层操作的有关函数;这个头文件包含了许多操作系统接口访问的重要功能;

而在这个头文件中存在个函数,这个函数为pid_t getpid(),其中这个函数的返回值类型pid_t可以看成是一个unsigned int类型;

该函数能够使该程序获取自身的进程标识符PID;

示例:

#include<iostream>

#include<unistd.h>

using namespace std;

void test1(){
  while(1){
	cout<<"the PID is  "<<getpid()<<endl;//打印出该进程的PID
	cout<<"##########################"<<endl;
 	sleep(1);
  }
}
int main()
{
  test1();
  return 0;
}

当运行这个程序之后这个进程将以间隔1s的速度打印出以上内容,同时带上PID;

在这里插入图片描述

使用ps查看该进程的状态:

$ ps axj | head -1 && ps axj | grep 32391 | grep -v grep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
31335 31336 31336 31336 pts/1    32391 Ss    1002   0:00 -bash
31336 32391 32391 31336 pts/1    32391 S+    1002   0:00 ./myproc

当使用kill -9 32391命令时向该进程发送9号信号将该进程杀死;

the PID is  32391
##########################
the PID is  32391
##########################
the PID is  32391
##########################
Killed
$ 

💻 父进程标识符PPID

在该头文件中一样存在着一个函数可以使得该程序获取当前进程的父进程的PID,也就是PPID;

#include<iostream>

#include<unistd.h>

using namespace std;

void test1(){
  while(1){
	cout<<"the PID is  "<<getpid()<<endl;//打印出该进程的PID
    cout<<"the PPID is  "<<getppid()<<endl;//打印出该进程父进程的PID
	cout<<"##########################"<<endl;
 	sleep(1);
  }
}
int main()
{
  test1();
  return 0;
}

当程序运行时:

the PID is  886
the PPID is  31336
##########################
the PID is  886
the PPID is  31336
##########################
the PID is  886
the PPID is  31336
##########################

使用ps也能直接的观察到该进程的PID与PPID从而进行验证;

ps axj | head -1 && ps axj | grep 886 | grep -v grep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
31336   886   886 31336 pts/1      886 S+    1002   0:00 ./myproc
31335 31336 31336 31336 pts/1      886 Ss    1002   0:00 -bash

然而从该处可以直接看到其实该进程的父进程就是一个-bash命令行解释器;


🖥️ 通过系统调用创建子进程 fork() 🖥️

unistd.h头文件中,还包含了一个函数pid_t fork();

该函数可以在当前进程中创建子进程;

而该函数的返回值分为三种:

  • 当该函数创建子进程失败则返回-1;
  • 当该进程为父进程时,该函数的返回值为子进程的PID;
  • 当该进程为子进程时,该函数的返回值为0;

示例:

#include<iostream>

#include<unistd.h>

using namespace std;

void test2(){
  cout<<"it'a test"<<endl;
  fork();
  cout<<"hello world"<<endl;
}

int main()
{
  test2();
  return 0;
}

当运行这段程序后所得的结果为:

$ ./myproc 
it'a test
hello world
hello world

本质上的原因是因为这个函数创建了子进程,而父子进程中代码是共享的,所以第一句代码只执行了一次,而fork()之后创建了子进程,第二句代码执行了两次的原因;

在这里插入图片描述


💻 那么为什么在fork()函数当中有两个返回值;

这里可以将fork()函数的内部看成两种代码,分别是最后的返回以及其他代码;

pid_t fork(){
    pid_t ret;
    //其他代码,主要用于创建子进程
    return ret;
}

从这里可以推断出,当该函数即将结束时,也就是在函数返回之前,其核心操作(创建子进程)已经结束;

当子进程被创建后代表将会多一个执行流用于最后一步的return;

在这里插入图片描述

实际上在操作系统当中,会为每个CPU准备一个对应的运行队列,其中这个运行队列当中的各个节点就相当于一个pcb结构体,同时这个节点中还存储着其对应的代码及数据;

当一个进程被运行时将会被操作系统加载到该CPU的运行队列中,而CPU采用分时多任务的方式对这些进程进行操作;

进程结束时这个进程将会被操作系统从运行队列中移出;

在这里插入图片描述

同时当子进程被创建之后将多一条执行流,而在接下来的代码当中是父进程先执行还是子进程先执行这是由操作系统的调度器来决定的;

在操作系统中不单单只有运行队列,在整体中CPU能算是一种资源,但是这个资源并不属于单单的一个进程,而是多个进程共用的;

对于其他的资源来说不同的资源都有着对应的队列(就绪队列,阻塞队列等),各个进程通过加载进对应的队列来实现对计算机当中各个资源的使用;


🔋 创建子进程后分区工作 🔋

在使用fork()创建子进程后子进程与父进程都将共享代码,那么在共享代码的前提下如何使得创建子进程后提高整体效率?

从上文可知,该函数在创建子进程时将有三种返回值,即>0父进程,==0子进程,<0创建进程失败;

可以在程序中使用分支语句根据不同的条件使得不同的进程来完成不同的工作;

示例:

#include<iostream>

#include<unistd.h>

using namespace std;

void test3(){
  pid_t ret = fork();
  if(ret<0) cerr<<"fork fail"<<endl;
  if(ret==0) while(1){
    cout<<"子进程 ret:"<<ret<<"  PID: "<<getpid()<<"  PPID: "<<getppid()<<endl;
  sleep(1);
  }
  if(ret>0)while(1){
    cout<<"父进程 ret:"<<ret<<"  PID: "<<getpid()<<"  PPID: "<<getppid()<<endl;
  sleep(1);
  }
}
int main()
{
  test3();
  return 0;
}

在该段代码中,使用了if分支语句将子进程与父进程进行分流工作从而达到子进程与父进程不无意义的重复同一项工作;

##结果
子进程 ret:0  PID: 13412  PPID: 13411
父进程 ret:13412  PID: 13411  PPID: 13265
子进程 ret:0  PID: 13412  PPID: 13411
父进程 ret:13412  PID: 13411  PPID: 13265

🖥️ 进程状态 🖥️

在操作系统中,进程也被称作为任务,而一个任务他的状态不一定是持续的;同时在操作系统中进程拥有多种状态,称之为进程状态;


💻 操作系统的进程状态

在操作系统中常见的进程状态有几种:

  • 新建状态

    当一个进程正在被创建时,操作系统将会为其分配系统资源,并初始化该进程对应的PCB结构体;


  • 运行状态
    当一个进程已经被加载至运行队列当中,这个进程即为运行态,进程的运行态并不代表这个进程将一直占用这个CPU,会以一种分时多任务的方式将CPU逐个分配运行队列中的各个进程,同时在这个时候,进程拥有了CPU资源和其他的所需资源;

在这里插入图片描述


  • 就绪状态
    当一个进程已经被创建,且已经分配好了其他资源,但操作系统还并未给该进程分配CPU资源,即该进程并未被加载至运行队列当中称之为就绪状态;


  • 阻塞状态
    当一个进程既没有CPU资源也没有其他所需资源时称之为阻塞状态;举一个例子:当一个进程原本在运行队列当中,但是这个进程中的某段代码需要去访问除了CPU外的其他资源(例如输入输出设备或者是其他设备时),这个进程将会从原本的运行队列加载至其他队列,这个队列就称之为阻塞队列,此时这个进程的状态就称之为阻塞状态;

在这里插入图片描述

即等待非CPU资源就绪称之为阻塞状态(调用cin但不输入数据);


  • 挂起状态
    相比于其他状态来说,挂起状态与CPU并没有太直接的关系;在计算机的磁盘当中,存在着一个swap分区,这个分区就是为了当内存不足时使进程状态转化为挂起状态而准备的;
    从上面可以知道,在队列当中的进程不仅仅是PCB结构体,由于各个进程要通过使用资源来操作自身的代码及数据可以得知一个进程中除了PCB结构体以外还包含着对应的代码以及数据;而挂起状态即为:
    当内存严重吃紧时,操作系统会将这个进程所对应的代码及数据交换至磁盘中的swap分区从而到达减轻内存的负担,当这个进程对应的代码及数据被交换至swap分区时这个进程即为挂起状态;

在这里插入图片描述


  • 阻塞挂起状态
    阻塞挂起状态,顾名思义,就是既是阻塞状态也是挂起状态;
    当一个进程在等待某种资源就绪时称这个进程的状态为阻塞状态,而当这个进程在等待某种资源时内存吃紧,导致该进程的代码与数据被操作系统移至磁盘的swap分区时,称之为阻塞挂起状态;

💻 Linux操作系统下的进程状态

在该处为了查看进程状态我们可以使用shell脚本语言来循环ps axj;

while : ; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; done #其中grep myproc 中的myproc为该进程的关键字
$ while : ; do ps axj | head -1 && ps ajx | grep myproc | grep -v grep ; sleep 2;  done
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

//状态在kernel源码中的定义

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", 		/* 0 */
"S (sleeping)", 	/* 1 */
"D (disk sleep)", 	/* 2 */
"T (stopped)", 		/* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", 		/* 16 */
"Z (zombie)", 		/* 32 */
};

实际上在Linux中的进程状态在内核中是一个指针数组static const char * const task_state_array[];


🔋 R 运行状态(running) 🔋

运行状态并不意味着该进程一直正在运行当中,当多个进程需要共享即同时使用同一个CPU时将会以分时多任务的方式将该资源分配给多个进程;

就像是打篮球时每个球员对于球的控制都有一定的时间,该在控球时间限制内将球传给另一个球员或者是其他操作;

存在一个程序,其代码为:

void test4(){
  while(1){
    cout<<"hello world"<<endl;
  }
}

int main()
{
  test4();
  return 0;
}

以前面的知识点可以知道当该程序运行时该进程应该为R状态,也就是running运行状态,而当运行这个程序后可以发现;

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383 16857 16857 13383 pts/2    16857 S+    1002   0:00 ./myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

当程序运行后发现该状态为S+状态,这是因为该程序当中该处调用了std::cout,意思是在该进程当中将会去调用显示器,而在冯诺依曼体系中显示器属于外设,所以当该进程需要去调用外设时这个进程将从调度队列转移到阻塞队列,所以显示出的状态为S+而不是R;

当代码中的cout<<"hello world"<<endl;被注释后代表这个程序不需要调用CPU以外的资源,此时这个程序再次被运行时进程状态将会变成R+;

void test4(){
  while(1){
    //cout<<"hello world"<<endl; //将该行注释
  }
}

进程状态如下:

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383 31313 31313 13383 pts/2    31313 R+    1002   0:00 ./myproc

此时的进程状态为R+;

同时该处的R状态对应着操作系统进程概念中的运行状态;


🔋 S 睡眠状态 (sleeping) 🔋

睡眠状态对应的是操作系统进程概念的阻塞状态,当一个进程在等待某种非CPU资源就绪时,称之为睡眠状态;

在测试运行状态的代码中也曾因为调用了外设出现了短暂的sleeping状态;

void test5(){
  int a = 0;
  cin>>a;//调用流提取,表示在运行这个程序时需要等待输入设备输入数据
}

int main()
{
  test5();
  return 0;
}

这段代码使用了流提取,所以需要等待输入设备(键盘)进行输入;

当该程序运行后发现这个进程的状态为S+:

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383  2620  2620 13383 pts/2     2620 S+    1002   0:00 ./myproc

但是该进程状态也被称作可中断睡眠(Interruptible sleep);

这里的可中断睡眠的意思即为可以被被动唤醒;

void test6(){
  sleep(1000);//使用sleep使该进程主动睡眠
}

int main()
{
  test6();
  return 0;
}

当运行该程序时其进程的状态为S+ 的状态;

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383  4437  4437 13383 pts/2     4437 S+    1002   0:00 ./myproc

从这里可以看出该进程的PID为4437;

而在这里若是使用-19信号使该进程被动唤醒;

kill -19 4439
PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383  4437  4437 13383 pts/2     4437 S+    1002   0:00 ./myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383  4437  4437 13383 pts/2    13383 T     1002   0:00 ./myproc

该进程的状态由S+转换为T状态,此时该进程的睡眠状态也已被唤醒;


🔋 D 磁盘休眠状态(Disk sleep) 🔋

磁盘睡眠也称作不可中断睡眠状态(uninterruptible sleep),也被称作深度睡眠,顾名思义即不可被被动唤醒;

在绝大多数情况下,磁盘的睡眠状态是可以被唤醒的,那么对于这个所谓的磁盘休眠状态到底是一种什么样的状态?

当一个进程向磁盘读写数据时,若是这个数据量足够大,则这个进程需要在这个资源继续等待;在等待过程中若是该进程被打断,则容易出现磁盘数据与内存数据不一致的问题;

当内存严重吃紧时,操作系统将会选择性的杀死一些等待过长的没有进行有效操作的进程,若是在进程对磁盘进行读写时将该进程杀死则会出现上述问题;

所以当一个进程向磁盘进行读写时,为了保证数据的一致性,在读写结束之前,即该进程得到磁盘的回复之前,该进程都是一种不可被打断的D 磁盘休眠状态;

在这个情况下,即使使用-9号信号也不能将该进程杀死;

本质上来说,这个不可中断睡眠是操作系统对进程和硬件的一种保护机制;

这个状态下的进程通常会等待IO的结束,这里的IO可能是磁盘IO,网络IO,其他外设的IO;


🔋 T(t) 停止、暂停状态(tracing stopped) 🔋

该状态也被称为暂停状态和调试状态,将一个程序以-g的形式编译并使用GDB进行调试,在打断点之后运行该程序,该程序停止时其进程的状态即为t状态;

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 7455  7542  7542 13383 pts/2     7455 t     1002   0:00 /home/root/Begin/my_-linux/Pr o23/Process1107/Test1121/myproc


🔋 T 停止、暂停状态(stopped) 🔋

可以通过发送SIGSTOP信号,即-19号信号停止该进程;

当该进程处于停止状态时,可以通过发送SIGCONT状态使其继续运行;

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383 11260 11260 13383 pts/2    11260 S+    1002   0:00 ./myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383 11260 11260 13383 pts/2    13383 T     1002   0:00 ./myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13383 11260 11260 13383 pts/2    13383 S     1002   0:00 ./myproc

在调试过程中可以理解为当遇到断点时GDB向进程发送SIGSTOP信号从而达到实现暂停进程;


🔋 X 死亡状态(dead) 🔋

当一个进程已经结束(死亡),但操作系统未及时回收该进程的资源时,这个进程称为死亡状态;

在任务列表当中一般看不到这个状态,是因为这个状态的瞬时性太强,同时该状态只是一个返回状态;


🔋 前台进程 🔋

在上述的进程状态描述当中,实际上出现的进程状态中带了一个符号+;

譬如S+,R+等等;

在操作系统当中,一般进程状态中带+的进程也被称作为前台进程;

前台进程占用的是命令行解释器,即当这个进程在运行时对应的终端不能对命令行进行解释;

同时前台进程可以直接使用Ctrl+C来结束;

$ ./myproc 
test
test
test
^C

若是想将该进程变为后台进程,可以在运行该程序时在最后带上一个&符号;

当使用这种方法将进程以后台进程的形式运行时将会为使用者回显一次该进程的PID,方便使用者对该进程进行对应的操作;

同时,使用这种方式之后,进程将不再占用命令行解释器也不能使用Ctrl+C结束,即命令行解释器可以正常使用;


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为ac+fit漫游配置案例

Ap漫游配置: 其它配置上面一样,ap管理dhcp和业务dhcp全在汇聚交换机 R1: interface GigabitEthernet0/0/0 ip address 11.1.1.1 255.255.255.0 ip route-static 12.2.2.0 255.255.255.0 11.1.1.2 ip route-static 192.168.0.0 255.255.0.0 11.1.1.2 lsw1: vlan batch 100 200…

dvwa 代码注入impossible代码审计

dvwa 代码注入impossible代码审计 <?phpif( isset( $_POST[ Submit ] ) ) {// Check Anti-CSRF tokencheckToken( $_REQUEST[ user_token ], $_SESSION[ session_token ], index.php ); // 检查token值是否正确// Get input$target $_REQUEST[ ip ]; $target stripslas…

Servlet执行流程Servlet 生命周期

Servlet 生命周期 对象的生命周期指一个对象从被创建到被销毁的整个过程 import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; WebServlet(urlPatterns "/demo",loadOnStartup 10) public class ServletDemo imple…

智能座舱架构与芯片- (5) 硬件篇 下

四、短距无线连接 随着汽车智能化的发展与新型电子电气架构的演进&#xff0c;传统车内有线通信技术存在着诸多痛点&#xff1a; 线束长度增加&#xff1a;由于智能化与自动化的发展&#xff0c;车内传感器和执行器均大幅增加。采用有线技术连接&#xff0c;则线束长度&#…

【C++】泛型编程 ⑩ ( 类模板的运算符重载 - 函数实现 写在类外部的同一个 cpp 代码中 | 类模板 的 外部友元函数二次编译问题 )

文章目录 一、类模板 - 函数声明与函数实现分离1、类模板 外部 实现 构造函数2、类模板 外部 实现 普通函数3、类模板 外部 实现 友元函数( 1 ) 错误示例及分析 - 类模板 的 外部友元函数 二次编译 问题( 2 ) 正确写法 二、代码示例 - 函数声明与函数实现分离1、代码示例2、执行…

【运维】永久关闭selinux不当,导致无法启动

现象: 卡centos loading进度条 按esc键发现,启动报错: Failed to load SElinux policy ,freezing 可能的原因: selinuxdisabled 写错成disable 或者 错误的把selinuxtype改了&#xff0c;要改文中红框的部分。 解决方案: 1. 重启 2. 出现选择画面的时候 按e 3. 方向下键…

树莓派的的串口通信协议

首先&#xff0c;回顾一下串口的核心知识点&#xff0c;也是面试重点&#xff1a; 串口通信通常使用在多机通讯中串口通信是全双工的决定串口通信的成功与否的是 数据格式 和 波特率数据格式&#xff1a;1. 数据位 2.停止位 3. 奇偶校验位 树莓派恢复串口 回忆前几节树莓派刷机…

时序预测 | Pytorch实现TCN-Transformer的时间序列预测

时序预测 | Pytorch实现TCN-Transformer的时间序列预测 目录 时序预测 | Pytorch实现TCN-Transformer的时间序列预测效果一览基本介绍程序设计 效果一览 基本介绍 基于TCN-Transformer模型的时间序列预测&#xff0c;可以用于做光伏发电功率预测&#xff0c;风速预测&#xff0…

【libGDX】使用Mesh绘制三角形

1 Mesh 和 ShaderProgram 简介 1.1 创建 Mesh 1&#xff09;Mesh 的构造方法 public Mesh(boolean isStatic, int maxVertices, int maxIndices, VertexAttribute... attributes) public Mesh(boolean isStatic, int maxVertices, int maxIndices, VertexAttributes attribut…

使用VSCode+PlatformIO搭建ESP32开发环境

Arduino IDE本来就是为创客们开发的&#xff0c;虽然没代码提示功能&#xff0c;文件的关系也不清晰&#xff0c;函数不能跳转&#xff0c;头文件也打不开&#xff0c;但人家的初衷就是为了简单而生的&#xff1b;但还是有一些同学喜欢高级点的IDE&#xff0c;也没问题&#xf…

局域网无法上网主机通过TinyProxy代理主机访问公网Internet

1.代理主机搭建: 系统:ubuntu 网卡:2个 运行于 VMWare上 第一个网卡用于NAT 第二个网卡用于私有网络 两个IP如下: 192.168.31.243为NAT可访问Internet 192.168.144.141属于私有网络,用于访问局域网 安装tinyproxy sudo apt install tinyproxy 查看服务状态

场景驱动的 AI 体验设计:如何让智能 IDE 赋能遗留系统重写

作为 AutoDev 的核心开发&#xff0c;我们不仅在不断丰富 AutoDev 的功能以满足不同公司的定制需求&#xff0c;还在与各种团队进行持续交流。在处理遗留系统时&#xff0c;我们发现程序员们日常工作中需要面对大量使用过时技术、基础设施混乱的系统。 在这个背景下&#xff0c…

智能座舱架构与芯片- (6) 显示篇 上

一、概述 在智能座舱的发展历程中&#xff0c;显示屏的个数越来越多&#xff0c;分辨率和显示屏的尺寸也越来越大。这已经是不可逆转的趋势。传统的座舱显示屏需要一颗主芯片支持一块屏幕&#xff0c;这在功能上和成本上都不是很好的做法。最新的智能座舱解决方案中&#xff0…

阿里 OSS鉴权访问文件

如果OSS文件设置保护&#xff0c;需要鉴权才能访问&#xff0c;添加请求头鉴权&#xff0c;SDK方法如上&#xff1b; 将鉴权信息和地址、时间返回给前端&#xff0c;前端直接从oss上读取 String filePath "/admin/2023/6/183569314928918546.png"; RequestMessage…

【python笔记】客户运营 - cohort分析

一、数据 本文涉及数据下载链接。 二、数据预处理 2.1 读取数据 import pandas as pddf pd.read_csv(your_path/Year 2010-2011.csv, encodingISO-8859-1) df.head()2.2 检查数据 检查空值情况 df.isna().sum() # 结果 Invoice 0 StockCode 0 De…

基于SpringBoot+MyBatis-Plus的校园图书管理系统

基于SpringBootMyBatis-Plus的校园图书管理系统 校园图书管理系统开发技术功能模块代码结构数据库设计运行截图源码获取 校园图书管理系统 欢迎访问此博客&#xff0c;是否为自己的毕业设计而担忧呢&#xff1f;是否感觉自己的时间不够做毕业设计呢&#xff1f;那你不妨看一下…

python趣味编程-5分钟实现一个简单弹跳球游戏(含源码、步骤讲解)

简单的Python弹跳球程序是使用Python编程语言开发的。 Python 中的弹跳球游戏是 使用 Tkinter 和图形用户界面 (GUI) 设计的,它是一个桌面应用程序。 Python 中的弹跳球游戏代码使用Canvas 在 Python 中绘制对象和随机模块。

ES7-ES13有何新特性?

目录 ES7 ES8 ES9 ES10 ES11 ES12 ES13 hello&#xff0c;大家好呀&#xff01;之前发布的两篇关于ES6新特性的文章学习完了吗&#xff1f;今天来给大家介绍ES6之后&#xff0c;截止到2022年的ES13每个时期新增的一些新特性&#xff01;快来一起学习吧&#xff01; ES7 …

【Computer Vision Foundation】全球计算机视觉基金会论文网

计算机视觉基金会&#xff08;Computer Vision Foundation&#xff0c;简称CVF&#xff09;是一个致力于推动计算机视觉领域研究和发展的组织。以下是关于计算机视觉基金会的一些基本信息&#xff1a; 成立目的&#xff1a; CVF成立的目的是促进计算机视觉领域的学术研究、技术…

【C+进阶之路】第六篇:C++11

文章目录 一、【C】C11&#xff08;1&#xff09;二、【C】C11&#xff08;2&#xff09; 一、【C】C11&#xff08;1&#xff09; 【C】C11&#xff08;1&#xff09; 二、【C】C11&#xff08;2&#xff09; 【C】C11&#xff08;2&#xff09; &#x1f339;&#x1f33…
最新文章