在并发程序设计中,主要有三种并发方式:多进程并发,基于IO复用并发,多线程并发。本节主要介绍多进程并发。以多客户端ehco程序为例。
1. 进程
进程是具有独立功能的程序关于某个数据集合的一次运行活动,是OS为正在运行的程序建立的管理实体,是系统资源管理与分配的基本单位。一个进程有五部分:操作系统管理该进程的数据结构(PCB),内存代码,内存数据,程序状态字PSW,通用寄存器信息。一个进程在OS中有四个基本状态。如图1.1所示。
图1.1 进程四态
挂起:挂起是OS收回进程的所有资源,将其移出内存。
创建进程时,实际上OS为其建立一个进程控制块,用于保存该进程的信息。多个进程同时运行时,其在内存中的状态如图1.2所示。
图1.2 多进程的内核状态
2. 多进程并发
进程是程序运行的基本单位,对于多内核的计算机,多个进程可以在多内核上同时运行,提高程序的并发性。如对于C/S类型的模型,客户端每发起一次通信,服务器开辟一个进程于其连接。这样实现服务器同时服务多个客户端。以经典的回声服务器,客户端为例,讲解多进程并发(注:Windows系统不支持,相关代码均以Linux系统为例)。
Linux系统的每个进程都有一个标志号,称为进程ID,其值大于2(1要分配给系统启动后的首个进程,用于协助操作系统)。在Linux系统中,创建一个进程采用fork函数
#include <unistd.h> pid_t fork(void); //pid_t为返回的ID号
调用fork函数之后,子进程创建,子进程会复制父进程的所有信息,然后从fork调用之后开始执行。那么怎么让父子进程执行不同的程序路径呢?这是通过主程序判断实现的,父进程调用fork函数,返回的是子进程的ID;而子进程的fork函数返回0,通过此返回值区别父子进程,从而控制fork函数之后的执行流。
2.1 僵尸进程
父进程fork子进程后,两个进程按各自的程序执行。父子进程结束时通过以下两种操作返回值并结束。
(1) 通过调用return语句返回;
(2) 通过exit()函数返回。
此返回值会保存至OS。但是子进程结束后,其返回值返回给操作系统(OS),此时OS并不会回收分配给子进程的所有资源。所以当父进程没执行完而子进程执行完成时,子进程资源没被回收,此时的子进程即为僵尸进程。僵尸进程会造成系统资源浪费。那么什么时候子进程资源会被回收呢?
(1) 当父进程结束之后;
(2) 当父进程向OS请求子进程返回值时。
因此为了结束僵尸进程,需要父进程主动向OS请求子进程的返回值。通过以下两种方式实现:
(1)父进程结束之前调用wait()函数
#include <sys/wait.h> int status; //保存返回时的状态信息 wait(&status); if(WIFEXITED(status))//WIFEXITED()在子进程正常终止时返回真 printf("Child return:%d",WEXITSTATUS(status)); //WEXITSTATUS获取子进程的返回值
一般有父进程创建了几个子进程就需要调用几次wait函数。调用wait函数后,父进程将阻塞直到有结束的子进程。
(2) 调用waitpid()
#include <sys/wait.h> pid_t waitpid(pid_t pid,int* statloc,int options); pid:等待的进程ID,若-1,则等待任一进程终止 statloc:用于保存返回状态的变量,与wait函数的参数一致 options:一般传递WNOHANG,意为非阻塞。
waitpid为非阻塞状态。
2.2