多进程编程 进程(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);
        }
    }
相关推荐
cwtlw2 分钟前
java基础知识面试题总结
java·开发语言·学习·面试
昵称为空C5 分钟前
SpringBoot编码技巧-ScheduledExecutorService轮询
java·spring boot·后端
小杨xyyyyyyy5 分钟前
JVM - 垃圾回收器常见问题
java·jvm·面试
西元.8 分钟前
多线程循环打印
java·开发语言·jvm
高林雨露8 分钟前
Kotlin 基础语法解析
android·开发语言·kotlin
ml1301852887415 分钟前
DeepSeek 助力心理医生小程序赋能!心理咨询小程序 线上咨询平台搭建
java·开发语言·小程序
不辉放弃15 分钟前
零基础讲解pandas
开发语言·python
用键盘当武器的秋刀鱼17 分钟前
springBoot统一响应类型3.5版本
java·spring boot·spring
A227417 分钟前
Netty——心跳监测机制
java·netty
Heliotrope_Sun37 分钟前
测试用例篇
java·测试用例