多进程编程 进程(Process)和线程(Thread)Linux环境 C语言实现

进程(Process)和线程(Thread)

**相同点:**二者都是操作系统的任务,都会参与时间片轮转,都有5种状态

不同点:

**1.**线程不能独立存在,只能隶属于创建它的进程。因此,进程其实是线程容器

**2.**进程是系统分配资源的基本单位

每个进程都有独立内存四区和描述符数组

而线程只有其独立运行必须的栈区,其它三区和描述符数组共用隶属的进程的

**3.**线程是参与时间片轮转的最小单位

进程(通过其主线程)和线程都会参与时间片轮转,但线程的体量更小,因此线程被称轻量级任务,进程被称重量级任务

4. 多进程比多线程更为安全

一个进程崩溃不会导致其它进程的崩溃

一个线程崩溃会导致其隶属的进程崩溃

**5.**线程间通信比进程间通信更为便利

进程间通信需要借助于系统提供的一些专门机制进行,而同属一个进程的多线程间可以借助于一块内存空间就能实现通信

进程是至少拥有一个主线程的线程容器,它是操作系统分配资源的基本单位、它通过主线程参与时间轮转,一个进程崩溃不会导致其它进程崩溃,进程间通信只能借助于操作系统提供的相关机制进行


一、进程的管理

Linux操作系统采用多种数据结构来管理众多的进程

其中一种是树形结构

除了祖先进程,其它进程都是由其父进程调用相关函数创建。

因此,系统在任何一个时刻,同时运行的所有进程组成一个家谱

当一个进程的父进程先退出,则该进程将会成为孤儿进程,但系统不会让其一直脱离进程树的管控,会让进程树中某个进程领养这个进程

Linux 系统采用如下方式处理孤儿进程:让祖先进程领养所有成为孤儿的进程


每个进程都有一个唯一的身份标识,称为 pid

进程代码中可以通过调用 getpid函数获取自身的身份标识

可以通过调用 getppid函数获取自身的身份标识


二、进程的退出

进程在以下情况正常退出:

  1. 进程的 main函数返回
  2. 进程中执行到 exit函数调用语句 ---- 中途安全退出
  3. 进程中执行到 _exit函数调用语句 ---- 中途安全退出
  4. 最后一个线程退出
  5. 最后一个线程执行了 pthread_exit函数调用语句

进程异常退出情况:

  1. 进程执行到 abort函数调用语句 ---- 中途异常退出
  2. 进程发生段错误
  3. 最后一个线程响应取消请求

实际开发过程中,应该需要保证进程正常退出,并建议采用main函数返回形式或最后一个线程退出两种形式退出整个进程

任何一个进程退出(无论是哪种方式),都不会立即消亡,而是成为僵尸态,直到其父进程或系统对它做完善后处理才会彻底消亡

处于僵尸态的进程被称为僵尸进程

显然僵尸进程是一种内存泄露错误,编程时需要对已退出进程立即最善后处理


三、进程的创建

一个进程中可以通过调用fork 函数创建一个子进程

总结:

  1. fork前只有父进程运行

  2. fork 成功后,父子进程除了存放fork返回值的变量值不同,其它内存空间以及描述符数组双方内容一致

    但除了代码区双方共享,其余三区和描述符数组各自用各自的(均为两份,一份给父进程用,另一份给子进程用)

  3. 在父进程中 fork 返回子进程的 pid ,在子进程中 fork 返回0,利用这一点可以决定后续代码哪些仅父进程执行,哪些仅子进程执行,哪些是双方都需要执行

进程相关命令:查看系统所有当前运行的进程

ps -aux :查看所有进程的 pid 、资源占用情况、状态(R ---运行态或就绪态 S ----睡眠态 Z----僵尸态)

ps -ef :查看所有进程的 pidppid


forkopen一个文件后的操作情况:

父子进程共用同一个引擎对象操作同一个文件(父子进程共用同一个位置指示器)

fork 后父子进程open同一个文件的操作情况


四、进程的善后

一个进程可能会有多个子进程,任何一个子进程退出,如果父进程不对该退出子进程进行及时的善后处理,就会导致该子进程长期处于僵死态。这种长期处于僵死态的进程被称为僵尸进程

僵尸进程已没有代码被执行,但仍然占用着一些系统资源,如果不及时(设延后t长时间)对其进行善后处理,则t长时间内这些被占用的系统资源无法被再度利用,造成t长时间的资源浪费。

父进程可以通过调用waitwaitpid 函数对已退出的子进程进行善后处理

可以使用带参宏 WEXITSTATUS (整数退出信息)中获取子进程 main 函数的返回值

如果不想获取已退出子进程的退出信息,形参wstatus 可以被传NULL


实际项目中,不能使用延时手段来让父子进程的代码段谁先运行谁后运行,但可以借助于wait 函数来实现

原因是 fork后父子进程是同时运行的,父进程的某行代码 与 子进程的某行代码谁先执行是由时间片轮转机制决定的,应用程序员无法明确!

wait函数只能实现子进程先运行完后父进程再执行的顺序逻辑,想要设计出其它的顺序逻辑需要借助并发控制机制实现


系统提供 wait 函数的目的是为了避免僵尸进程,但有时候父进程可能会无法使用wait 函数来对已退出子进程及时进行善后,例如:

因此,wait函数也不能处理任意情况的子进程及时善后,避免僵尸进程的方法:

  1. 父进程调用 wait 函数

  2. 让父进程创建子进程后,子进程里再创建孙子进程,而子进程生完孙子立即退出,孙子进程成为孤儿进程。然后会被祖先进程领养,以后孙子进程退出善后就由祖先进程负责

cpp 复制代码
pid_t cpid;
pid_t gpid;

cpid = fork();

if(cpid == 0){
	gpid = fork();
	if(gpid == 0){
		//doing something
	}
	else{
		exit(0);
	}
}
else{
	wait(NULL);//善后子进程
	//。。。。。。。
}
  1. 在父进程 fork 前,调用 signal(SIGCHLD,SIG_IGN); ---- 通知系统,本进程的所有子进程退出善后由系统自动进行

练习: 编写程序完成如下功能,父进程创建子进程前动态分配一块大小为20 的内存空间,子进程向动态空间中拷贝字符串**"hello"** ,打印后退出,父进程等待子进程退出后,向动态空间拷贝字符串**"world"**,打印后退出

代码:

cpp 复制代码
#include <sys/types.h>
#include <unistd.h>

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

int main(int argc,char *argv[]){
	pid_t pid;
	char *pbuf = NULL;

	//仅父进程在运行
	pbuf = malloc(20);

	pid = fork(); // fork一个子进程
	if(pid < 0){
		printf("fork child-process failed\n");
		return 1;
	}

	if(pid > 0){//仅父进程执行
		printf("pid=%d,aaaaaaa\n",getpid());
		strcpy(pbuf,"hello");
	}
	else{//仅子进程执行
		printf("pid=%d,bbbbbbb\n",getpid());
		strcpy(pbuf,"world");
	}

	//父、子进程都需执行的代码
	printf("pbuf = %s\n",pbuf);
	free(pbuf);
	pbuf = NULL;

	return 0;
}

输出:


五、进程的替换

fork 子进程的目的:

为了让父子进程共用同一份代码,做相同或不同的事务 ------ 网络编程的服务端常用

为了让子进程去执行一个新可执行文件代表的程序 ------ 进程的替换

execl
execlp
execv
execvp

之间课程里说main函数argv[0]指向空间内容是可执行文件名本身,那是shell命令行程序默认形式,自己在代码中调用exec无需遵循shell命令行的规定


六、system函数

七、精灵进程或守护进程 daemon-process

满足如下特征的进程:

  1. 没有控制终端---无法在屏幕上打印信息也无法接收键盘输入

  2. 后台执行

    让自己的程序后台执行:./test &

    让自己的程序前台执行:./test

  3. 系统自举时启动,系统关闭时终止

  4. 进程组的组长进程以及会话的首进程,也是唯一进程

    进程组:一个或多个进程的集合,组长进程是指创建进程组的进程,组长进程的pid就是进程组的组id

    会话:一个或多个进程组的集合,会话首进程是指创建会话的进程

  5. 父进程是祖先进程

  6. 以超级用户身份特权运行


    调用如下函数可以让自己编写的程序运行起来后成为守护进程

    cpp 复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <syslog.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/resource.h>
    #include <unistd.h>
    #include <signal.h>
    
    void daemonize(const char *cmd)
    {
        int                 i, fd0, fd1, fd2;
        pid_t               pid;
        struct rlimit       rl;
        struct sigaction    sa;
        /*
         * Clear file creation mask.
         */
        umask(0);
    
        /*
         * Get maximum number of file descriptors.
         */
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
            printf("%s: can't get file limit", cmd);
    
        /*
         * Become a session leader to lose controlling TTY.
         */
        if ((pid = fork()) < 0)
            printf("%s: can't fork", cmd);
        else if (pid != 0) /* parent */
            exit(0);
        setsid();
    
        /*
         * Ensure future opens won't allocate controlling TTYs.
         * 通过再次创建子进程结束当前进程,使进程不再是会话首进程来禁止进程重新打开控制终端
         */
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
            printf("can't ignore SIGHUP");
        if ((pid = fork()) < 0)
            printf("%s: can't fork", cmd);
        else if (pid != 0) /* parent */
            exit(0);
    
        /*
         * Change the current working directory to the root so
         * we won't prevent file systems from being unmounted.
         */
        if (chdir("/") < 0)
            printf("can't change directory to /");
    
        /*
         * Close all open file descriptors.
         */
        if (rl.rlim_max == RLIM_INFINITY)
            rl.rlim_max = 1024;
        for (i = 0; i < rl.rlim_max; i++)
            close(i);
    
        /*
         * Attach file descriptors 0, 1, and 2 to /dev/null.
         */
        fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);
    
        /*
         * Initialize the log file.
         */
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
            syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
              fd0, fd1, fd2);
            exit(1);
        }
    }
相关推荐
Achou.Wang18 分钟前
剖析go协程池实现原理
android·java·golang
huisheng_qaq2 小时前
【Spring源码核心篇-06】spring中事务的底层实现与执行流程
java·spring·事务·aop·动态代理·spring源码·trancational
统信软件技术有限公司2 小时前
统信服务器操作系统V20系列配置JDK方案
java·运维·服务器
若雨叶2 小时前
parallelStream并行流使用踩坑,集合安全
java·开发语言·windows
mingyuewu2 小时前
JAVA中的@Builder是什么意思
java
Derrick_itRose3 小时前
黑马程序员MybatisPlus/Docker相关内容
java·docker
南宫生3 小时前
力扣【算法学习day.50】
java·学习·算法·leetcode
hunandede3 小时前
avcodec_alloc_context3,avcodec_open2,avcodec_free_context,avcodec_close
java·开发语言
Java学长-kirito3 小时前
springboot/ssm旅游民宿信息管理系统Java旅游景点管理系统web旅游源码
java·spring boot·旅游
pk_xz1234563 小时前
Python爬虫
开发语言·爬虫·python