• 多进程编程(一):基本概念


    1、基本概念

    • 进程: 一个正在执行的应用程序,是操作系统 资源分配 的基本单元
    • 线程: 是正在执行的应用程序里面的一个具体任务,是操作系统 资源调度 的基本单元
    • 进程和线程间的关系:
      进程可以包括很多个线程,为线程提供必备的资源,所有的线程共享这些资源。每个线程负责完成一个具体的任务,线程间相互配合,共同保证进程正常运行。同时,线程也有自己的私有资源。

    2、进程的状态

    • 就绪态、运行态、阻塞态
      在这里插入图片描述
      运行态: 进程占有处理器正在运行
      就绪态: 进程具备运行的条件,正在等待系统分配处理器,然后开始运行
      阻塞态: 进程不能够直接运行,正在等待某个事件结束,然后进入就绪态

    Linux环境下,查看筛选进程的shell命令

    • ps -ef :查看系统的全部进程
    • ps aux :查看系统的全部进程
    • ps -ef | grep demo :从全部进程中,筛选出和 demo 相关的进程
    • 杀死进程:kill -9 pid_t

    3、创建进程

    fork()
    • 必备头文件
      #include <sys/types.h>
      #include <unistd.h>
      
      • 1
      • 2
    • 用于产生一个子进程,函数返回值pid_t是一个整数,在父进程中,返回值是子进程编号;在子进程中,返回值是0。如果创建失败,则返回 -1。
      pid_t fork(void);
      
      • 1
    • 特点:
      1、fork()函数所创建的子进程是父进程的完整副本,复制了父进程的资源
      写时复制:刚创建子进程后,父子进程对于变量是共享的,只要在任一进程对数据执行了写操作时,复制才会发生(数据就不共享了,先是缺页中断,然后操作系统给子进程分配内存,并复制父进程的数据)。
      2、子进程拥有自己的虚拟地址空间,父子进程数据独有,代码共享(fork()函数后的代码
      3、根据返回值,来判断是父进程还是子进程
    • 举例:
      #include <stdio.h>
      #include <sys/types.h>
      #include <unistd.h>
      
      int main() {
      	pid_t res;
      	res = fork();
      
      	if (res > 0) {
      		printf("This is parent, pid = %d\n", getpid());
      	} else if (res == 0) {
      		printf("This is child, pid = %d\n", getpid());
      	} else {
      		perror("fork");
      	}
      
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      打印输出
      This is parent, pid = 3543
      This is child, pid = 3544
      
      • 1
      • 2
    vfork()
    • 必备头文件

      #include <sys/types.h>
      #include <unistd.h>
      pid_t vfork(void);
      
      • 1
      • 2
      • 3
    • 特点:
      1、返回值和fork()函数相同。
      2、vfork()不创建子进程的虚拟地址,直接共享父进程的,从而物理地址也被共享了
      3、子进程先运行,在子进程调用 exec(进程替换)或者exit之后,父进程被调度执行

    • 举例:

      #include <stdio.h>
      #include <sys/types.h>
      #include <unistd.h>
      #include <stdlib.h>
      
      int main() {
      	int num = 10;
      
      	pid_t res;
      	res = vfork();
      
      	if (res > 0) {
      		printf("This is parent, pid = %d\n", getpid());
      		num += 10;
      		printf("num = %d\n", num);
      
      	} else if (res == 0) {
      		printf("This is child, pid = %d\n", getpid());
      		num += 10;
      		printf("num = %d\n", num);
      		exit(0);
      	} else {
      		perror("vfork");
      	}
      
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27

      打印输出

      This is child, pid = 4495
      num = 20
      This is parent, pid = 4494
      num = 30
      
      • 1
      • 2
      • 3
      • 4

      从输出结果可以看出,子进程先执行,在子进程调用 exit(0)退出之后,父进程再执行。
      并且,父子进程共享 num变量。

    fork()vfork()的不同之处:
    • fork()复制父进程的页表项,当进行写操作时,内核给子进程分配一个新的内存页面
      vfork()与父进程共享页表项,当写操作时,直接写在父进程的内存页面
    • fork()创建的子进程与父进程之间的执行次序不确定
      vfork()是子进程先运行,在子进程调用 exec(进程替换)或者exit之后,父进程被调度执行
    • vfork()保证子进程先运行,在子进程调用execexit之后父进程才可能被调度运行。如果在
      调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

    4、exec函数族

    有时候,我们需要在子进程中执行其它程序,即替换当前进程映像,这时候会使用exec函数族中的一些函数。
    **作用:**根据指定的文件名找到可执行文件,并用它来取代调用进程的内容(在调用进程内部执行一个可执行文件)
    返回值: exec函数族的函数执行成功后不会返回,只有调用失败了,才会返回-1,回到源程序的调用点接着往下执行。

    常用函数:

    • 1、

      int execl(const char *pathname, const char *arg, ...);
      - pathname: 要执行的可执行文件的路径
      - arg: 执行可执行文件所需要的参数列表
      	arg一般填写当前执行程序的名字,后面依次是可执行程序所需要的参数列表,参数最后要以 NULL 结束。
      - 返回值:
      	当调用失败时,才有返回值,返回-1
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 2、

      int execlp(const char *file, const char *args, ...);
      - 和 execl 基本一致,不过对于file会从环境变量中寻找。
      
      • 1
      • 2

    5、孤儿进程

    定义: 父进程结束运行,但是子进程还在运行,这样的子进程为孤儿进程(父死子在)。
    注意: 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init,而 init进程会循环地 wait() 它的已经退出的子进程。这样当一个孤儿进程结束生命周期的时候,init()进程就会处理它的一切善后工作。
    因此,孤儿进程并不会有什么危害。

    6、僵尸进程

    当子进程结束运行时,内核不会立即释放该进程的进程表表项,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。
    在子进程结束运行后,父进程回收子进程状态前,该子进程为僵尸进程。内核资源有限,不允许产生大量的僵尸进程。
    僵尸进程不能被 kill -9 杀死。
    可以在父进程中使用 wait() 或者 waitpid() 函数,以等待子进程的结束,并获取子进程的返回信息,从而避免了僵尸进程的产生,或者使子进程的僵尸态立即结束。

    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *wstatus);
    - 功能:等待任意一个子进程结束,并回收子进程。
    - 参数:wstatus, 进程退出时的状态信息,传入的是一个int类型的指针(传出参数)
    - 返回值:
    	成功:返回被回收的子进程的id
    	失败:-1(所有子进程都结束,调用函数失败)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意: 调用 wait() 函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒。
    如果没有子进程了,函数立刻返回-1;
    如果子进程都已经结束了,也会立即返回,返回 -1。

  • 相关阅读:
    深度解析:会用Excel,还有必要学Python吗?
    P8352-[SDOI/SXOI2022]小N的独立集【dp套dp】
    Vue3渲染&事件处理
    springboot+mybatis拦截器实现水平分表操作
    什么是软件?什么是瀑布模型?
    Python+Appium自动化测试-编写自动化脚本
    基于划分的聚类分析——K-means(机器学习)
    Excel基础2
    全栈自动化测试之pytest常用命令行参数
    【opencv-c++】FastLineDetector快速线段检测器
  • 原文地址:https://blog.csdn.net/Sir666888/article/details/125454930