目录
一:孤儿进程
什么是孤儿进程
父进程先结束,子进程的父进程id由系统决定
pid_t getpid(void); 返回:调用进程的 进程I D
pid_t getppid(void); 返回:调用进程的 父进程I D
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int number = 0;
int main()
{
pid_t id = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
}
else if (id == 0)
{
for (int i = 0; i < 30; i++)
{
number++;
cout << "子进程 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
cout << "over" << getpid() << endl;
return 0;
}
终端编译运行
g++ main.cpp -o main
./main
父进程会走10次
原先,子进程的父进程ID和父进程pid同(子进程就是由父进程创建的),
当父进程走完10次后,子进程的父进程ID为1435(由操作系统自己分配的)
可以通过ps -aux查看进程 查看PID进程号
发现原先子进程pid62766 以及 原先子进程的父进程ID62765已经不存在了
父进程走完10次后,子进程的父进程ID为1435,这个PID存在
对于这个子进程(父进程先结束),称为:孤儿进程
孤儿进程:
父进程先于子进程执行完逻辑(父进程死亡子进程还在执行)
二:僵尸进程
做个测试:子进程先结束
for循环设置父进程30,设置子进程10
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int number = 0;
int main()
{
pid_t id = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
for (int i = 0; i < 30; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
}
else if (id == 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "子进程 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
cout << "over" << getpid() << endl;
return 0;
}
子进程先于父进程结束,但看出63124 63125(PID)进程都存在,
查看进程,进程状态中看出子进程63125是Z+僵尸状态 ,对于僵尸状态,虽然子进程不做事,但是依然占用系统资源
僵尸进程:子进程先于父进程执行完逻辑(子进程死亡父进程还在执行)
程序完全结束:对于现在学习的多进程,要求当前这个程序的所有的进程都结束,才算是真正意义上的程序完全结束
现实中,比如APP应用程序微信,如果关微信,再清理后台,但是别人发微信消息给自己,仍然可以接收,只是看不到微信界面而已。可以把界面看作一个进程,接收消息的是另外的一个进程,把界面推掉不叫程序退出(只是关闭一个进程),但只要在线有网络,依然能接收消息,手机(操作系统),只要手机运行程序(就算作是一个进程 占用内存)。有些进程可见(如微信界面),有些进程不可见(如接收消息)
三:wait/waitpid函数 & exit函数
怎么避免出现孤儿进程,僵尸进程?
wait和waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号
因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知
父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)
对于这种信号的系统默认动作是忽略它
(wait函数用于使父进程阻塞),直到一个子进程结束或者该进程接收到一个指定信号为止
fork代码套路升级
if(pid > 0)
{
父进程逻辑
wait()/waitpid()
}
else if(pid == 0)
{
子进程逻辑
exit()
}
避免孤儿进程:父进程要等待子进程运行结束,父进程再结束
wait函数用于使父进程阻塞
目标:要等子进程先结束,然后父进程再结束
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int number = 0;
int status = 0;
pid_t id = 0;
pid_t waitid = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
waitid = wait(&status);//阻塞式函数:导致之后的逻辑卡住不动,直到某一件事情发生,这个函数才会执行结束,接收返回值
cout << "waitid = " << waitid << endl;
}
else if (id == 0)
{
for (int i = 0; i < 20; i++)
{
number++;
cout << "子进程 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
cout << "over" << getpid() << endl;
return 0;
}
结果:等到子进程先结束,然后父进程再结束
测试一下:如果有两个不同for循环次数的子进程呢?
是否会等待慢的那个子进程结束吗?
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int number = 0;
int status = 0;
pid_t id = 0;
pid_t otherid = 0;
pid_t waitid = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
otherid = fork();
if (otherid > 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
waitid = wait(&status);
cout << "waitid = " << waitid << endl;
}
else if (otherid == 0)
{
for (int i = 0; i < 15; i++)
{
number++;
cout << "子进程1 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
}
else if (id == 0)
{
for (int i = 0; i < 25; i++)
{
number++;
cout << "子进程2 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
cout << "over" << getpid() << endl;
return 0;
}
waitid = 64670
说明:wait函数:等待子进程逻辑结束,只要有一个子进程结束wait就会返回
但是,需要两个等待两个子进程都结束,因此wait函数不可行 ,需要学习waitpid函数
waitpid函数使用三个参数
waitid = waitpid(id, &status, NULL);
具体测试一下
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int number = 0;
int status = 0;
pid_t id = 0;
pid_t otherid = 0;
pid_t waitid = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
otherid = fork();
if (otherid > 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
//waitid = wait(&status);
waitid = waitpid(id, &status, NULL);
cout << "waitid = " << waitid << endl;
}
else if (otherid == 0)
{
for (int i = 0; i < 15; i++)
{
number++;
cout << "子进程1 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
}
else if (id == 0)
{
for (int i = 0; i < 25; i++)
{
number++;
cout << "子进程2 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
}
cout << "over" << getpid() << endl;
return 0;
}
waitid = 65211 也就是等待所有子进程都结束,父进程才会结束,避免孤儿进程
waitpid:等待指定的某一个子进程结束
若是用状态查看来查看是否等待所有子进程结束:
if (WIFEXITED(status))
{
cout << "退出码=" << WEXITSTATUS(status) << endl;
}
具体测试如下
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int number = 0;
int status = 0;
pid_t id = 0;
pid_t otherid = 0;
pid_t waitid = 0;
//一次调用 两次返回
id = fork();
if (id > 0)
{
otherid = fork();
if (otherid > 0)
{
for (int i = 0; i < 10; i++)
{
number++;
cout << "父进程 pid = " << getpid() << endl;
sleep(1);
}
//waitid = wait(&status);
waitid = waitpid(id, &status, NULL);
cout << "waitid = " << waitid << endl;
if (WIFEXITED(status))
{
cout << "退出码=" << WEXITSTATUS(status) << endl;
}
}
else if (otherid == 0)
{
for (int i = 0; i < 15; i++)
{
number++;
cout << "子进程1 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
exit(1);
}
}
else if (id == 0)
{
for (int i = 0; i < 25; i++)
{
number++;
cout << "子进程2 pid =" << getpid() << "父进程ID" << getppid() << endl;
sleep(1);
}
exit(2);
}
cout << "over" << getpid() << endl;
return 0;
}
退出码 = 2,就是等待子进程2结束,再结束父进程 【exit(2)】
对于这个子进程状态返回,举个例子,比如QQ,聊天当作一个主进程,传输文件当作一个子进程,如果传输文件成功wxit返回,主进程知道文件传输成功
同样文件传输失败exit也可以告知主进程文件传输失败
exit有比较重要的作用 知道返回
避免僵尸进程:子进程执行完逻辑执行exit 结束进程
(子进程中break或者return仍然有可能僵尸进程,但是exit是可以避免僵尸进程的)
利用wait 和waitpid既可以解决孤儿进程又可以解决僵尸进程
多进程框架结构:
既不会出现僵尸,又不会出现孤儿,框架设计如下
pid = fork();
if(pid>0)
{
...
wait()/waitpid();//看有几个进程来决定 一个子进程用wait 多个子进程用waitpid
}
else if(pid == 0)
{
...
exit(0);
}
四:进程状态
运行状态R(TASK_RUNNING)
可中断睡眠状态S(TASK_INTERRUPTIBLE)
不可中断睡眠状态D(TASK_UNINTERRUPTIBLE)
暂停状态T(TASK_STOPPED或TASK_TRACED)
僵尸状态Z(TASK_ZOMBIE)
退出状态X(TASK_DEAD)
进程状态:
新建->就绪->运行->挂起->终止
任务管理器可以看到的进程只有运行状态的进程状态
运行细分为:根据不同的代码场景有不同的状态如下
可唤醒睡眠(S):sleep
不可唤醒睡眠(D)
等待:wait
僵尸(Z) :子进程先于父进程执行完
暂停(T)