热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【C/C++】多进程:父进程监听子进程状态wait()的使用

文章结构:wait能力介绍wait()函数讲解示例代码及操作演示wait能力介绍在上一篇【CC++】多进程:子进程的创建fork()中演示了子进程的创建。创建子进程后,父进

文章结构:

  • wait能力介绍
  • wait()函数讲解
  • 示例代码及操作演示

wait能力介绍

  在上一篇【C/C++】多进程:子进程的创建fork()中演示了子进程的创建。

  创建子进程后,父进程具有监听子进程的运行状态的能力,用到的函数为:

   #include 

   pid_t wait(int *status);
   pid_t waitpid(pid_t pid, int *status, int options);

  以上函数用于等待子进程子进程的状态变化回调并且获取状态变化信息。所能获取到的状态变化包括:子进程运行结束、子进程被信号量暂停、子进程被信号量恢复运行。

  父进程执行了wait函数后,如果子进程已经发生了状态变化,则wait函数立即就会有返回结果;否则wait函数会一直阻塞直至子进程状态发生变化。

  通常意义上,如果子进程已经发生了状态变化,但还未被父进程或其它系统回调执行wait,则把此时的子进程称为是可等待的(waitable)。

  子进程运行结束后,父进行执行wait函数可以推动系统释放与子进程相关的资源;否则子进程将会被维持在僵尸进程的状态下一直存在。   


wait()函数讲解

  函数wait(int * status)是对waitpid()的封装,限定了只有在任一子进程运行结束时才会有返回,否则调用进程会一起处于阻塞状态暂停执行。wait(int * status)等同于如下代码:

       waitpid(-1, &status, 0);

  waitpid()会阻塞调用进程直至任一子进程的运行状态发生变化。接下来对waitpid()的三个参数进行讲解:   

  • pid

  pid <-1取该pid的绝对值,如果任意子进程的进程组ID等于该绝对值,则该组进程中任一子进程中的进程状态发生变化都会触发waitpid()的回调。

  pid == -1监听范围扩大到任意子进程。

  pid == 0监听限制为子进程的进程组ID与父进程相等。

  pid > 0监听限制为指定子进程进程ID值。

  • status

  值可以为NULL。当不为NULL时,用于存储触发状态变化的信息号值和exit(code)中的code值。

  wait.h头文件定义了几个宏用于解析status的值,常见的有:

含义
WIFEXITED(status)

WEXITSTATUS(status)
当子进程调用exit(code)_exit(code)或正常运行到main()函数结尾时正常结束运行,则返回true

WIFEXITED(status)true时,获取exit(code)_exit(code)code值。
其中code只能为0或正数,不支持负数。
WIFSIGNALED(status)

WTERMSIG(status)
当子进程被信号量杀死时则返回true

WIFSIGNALED(status)true时,获取该信号量的值。
WIFSTOPPED(status)

WSTOPSIG(status)
当子进程被信号量暂停执行时则返回true

WIFSTOPPED(status)true时,获取该信号量的值。
  • options

  值可以是以下常量的任意值或任意常量与0的OR计算值。

常量含义
WNOHANG调用wait时指定的pid仍未结束运行,则wait立即返回0。
WUNTRACED当子进程被暂停时,则wait立即返回子进程的pid
WCONTINUED
当被暂停的子进程又被信号量恢复后,则wait立即返回子进程的pid
Linux 2.6.10及以后生效。在Mac 0S X 10.9.5上未生效。

  wait()函数在正常执行时会返回被终止进程的pid值,当执行发生错误后会返回-1。
  waitpid()函数在正常执行时会返回进程状态发生变化的进程pid值;如果函数options中包含了WNOHANG常量,则会在指定pid的子进程未退出且进程状态也未发生变化时直接返回0,如果子进程已经退出了,则返回子进程的pid;否则当执行发生错误后会返回-1。


示例代码及操作演示

  由于涉及到两个进程,在终端命令行下的日志打印会出现混乱,所以通过重定向标准输入输出流将两个进程的日志分别输出到两个文件中去,父进程的日志输出到main.txt中去,子进程的日志输出到child.txt中去。涉及到重定向标准输入输出流,具体细节见【C/C++】文件创建、打开、读、写、复制、关闭、删除等操作汇总。

  涉及到的pskill命令可以通过man psman kill去查找细节。

  接下来的代码将演示fork()一个子进程后,会通过ps命令查询进程状态,及kill命令向子进程发送信号量改变进程状态;父进程通过wait监听子进程状态。

  wait.c源码如下:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include
#include
#include
#include
#include
#include
#include
#ifndef CHILD_COUNT
#define CHILD_COUNT 10000
#endif
static void printTime() {
time_t calendar_time = time(NULL);
struct tm * tm_local = localtime(&calendar_time);
char str_f_t [50];
strftime(str_f_t, sizeof(str_f_t), "%G-%m-%d %H:%M:%S", tm_local);
printf("%s ", str_f_t);
}
int main/*09*/(int argc, char *argv[]) {
pid_t cpid, w;
int status;
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) {
/* Code executed by child */
FILE* fChild = freopen("/Users/sodino/workspace/xcode/Define/Define/child.txt", "w", stdout);
printTime();
printf("Child PID is %ld argc=%d\n", (long) getpid(), argc);
int count = 0;
do{
sleep(1);
printTime();
printf("sleep count=%d\n", count);
fflush(fChild);
count ++;
if (count > CHILD_COUNT) {
break;
}
}while (1);
fflush(fChild);
fclose(fChild);
// code值将从父进程的WEXITSTATUS()宏定义中获知
// _exit(123);
exit(123);
} else {
FILE * fMain = freopen("/Users/sodino/workspace/xcode/Define/Define/main.txt", "w", stdout);
// 如果子进程还在,则返回0;如果子进程已经运行结束,则返回子进程pid
w = waitpid(cpid, &status, WNOHANG);
if (w == 0) {
printTime();
printf("Child PID=%d has not yet changed state\n", cpid);
} else {
printTime();
printf("Child PID=%d has been terminated.\n", cpid);
}
int ifExited, ifSignaled, ifStopped, ifContinued;
/* Code executed by parent */
do {
// 在mac上·WCONTINUED·无效,从头文件来看,只适用于thread
w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
// w = waitpid(cpid, &status, WUNTRACED);
if (w == -1) {
printTime();
printf("Parent w=-1, error=%s \n", strerror(errno));
exit(EXIT_FAILURE);
} if (w == 0) {
printTime();
printf("w == 0 ignore. continue.\n");
continue;
}
ifExited = WIFEXITED(status);
ifSignaled = WIFSIGNALED(status);
ifStopped = WIFSTOPPED(status);
ifCOntinued= WIFCONTINUED(status);
printTime();
printf("pid=%ld w=%d exitCode=%d status=%d ifExited=%d ifSignaled=%d ifStopped=%d ifCOntinued=%d \n", (long)getpid(),
// w, status, WEXITSTATUS(w), ifExited, ifSignaled, ifStopped, ifContinued);
w, status, _WSTATUS(w), ifExited, ifSignaled, ifStopped, ifContinued);
printTime();
if (ifExited) {
printf("PID=%ld exited, status=%d\n", (long)w, WEXITSTATUS(status));
} else if (ifSignaled) {
printf("PID=%ld killed by signal %d\n", (long)w, WTERMSIG(status));
} else if (ifStopped) {
printf("PID=%ld stopped by signal %d\n", (long)w, WSTOPSIG(status));
} else if (ifContinued) {
printf("PID=%ld continued\n", (long)w);
}
fflush(fMain);
if (ifExited || ifSignaled) {
printTime();
printf("isExited=%d isSingaled=%d\n", ifExited, ifSignaled);
fflush(fMain);
break;
}
} while (1);
printTime();
printf("Main PID %ld exit.\n", (long)getpid());
fclose(fMain);
exit(EXIT_SUCCESS);
}
}

  进入到wait.c的目录下,执行如下命令编译并运行:

   gcc wait.c // 生成可执行文件a.out
   ./a.out    // 运行可执行文件

  使用ps -j命令,查看进程可以看到fork()执行后,进程列表中有两个名为a.out的进程。如下图:
技术分享

  可以看到PID=678的进程其父进程是PID=677。两个进程的进程组ID(PGID)都为677。

  再看上图中的STAT列,两个进程都是S+,其中S表示进程处于sleeping状态,原因是父进程在wait,而子进程除了打印日志外大部分时间都是在执行sleep();另一个+表示这两个进程都是在当前控制台的前台进程。

  接使用kill -SIGSTOP 678命令向子进程发送暂停信号,再ps -j查询一下进程状态,发现子进程678已经从S+变为T+,即已经进入STOP状态了。

技术分享

  而另一方面,查看child.txt,发现该文件已经不会继续生成日志了。查看main.txt文件,日志内容如下:

  2015-04-16 22:53:15 Child PID=678 has not yet changed state
  2015-04-16 23:11:48 pid=677 w=678 exitCode=4479 status=38 ifExited=0 ifSignaled=0 ifStopped=1 ifCOntinued=0 
  2015-04-16 23:11:48 PID=678 stopped by signal 17

  第一句Child PID=678 has not yet changed state是父进程执行waitpid:WNOHANG的返回结果,表示当时子进程仍未退出,正在运行。第二句则输出了ifStopped=1表示waitpid:WUNTRACED已经监听到子进程被外部发送的信号量导致进程状态发生变化了。第三句PID=678 stopped by signal 17表示信号量值是17,通过以下命令可能验证SIGSTOP信号量值为17:

  sodino:Define sodino$ kill -l SIGSTOP
  17

  接下来,要继续使用kill -SIGCONT 678命令来恢复子进程的运行,操作见下图:
技术分享

  由上图可见子进程从暂停状态又恢复运行了。查看child.txt日志则发现日志又恢复输出了:

   ... ...
   2015-04-16 23:11:46 sleep count=1110
   2015-04-16 23:11:47 sleep count=1111
   2015-04-16 23:23:20 sleep count=1112
   2015-04-16 23:23:21 sleep count=1113
   ... ...

  注意看时间,从23:11分到23:23分这个时间段内进程状态是停止的所以没有日志输出(这段时间在写博客给你们看呀..)。查看main.txt则没有发现waitpid有回调,个人认为这是在我的mac上C版本及运行环境问题吧。

  最后再使用命令kill -SIGTERM 678后,可以发现ps命令已经查询不到刚才的a.out进程了。  
技术分享

  查看main.txt可以发现waitpid有了返回值其中ifSignaled=1表示子进程已经被信号量15所杀死。并最后退出了。

 2015-04-16 23:31:00 pid=677 w=678 exitCode=15 status=38 ifExited=0 ifSignaled=1 ifStopped=0 ifCOntinued=0 
 2015-04-16 23:31:00 PID=678 killed by signal 15
 2015-04-16 23:31:00 isExited=0   isSingaled=1
 2015-04-16 23:31:00 Main PID 677  exit.

  以上,就是本篇想说的,下一篇:多进程:僵尸进程

【C/C++】多进程:父进程监听子进程状态 wait()的使用


推荐阅读
  • 在HDU 1166敌军布阵问题中,通过运用线段树数据结构,可以高效地计算指定区间的敌军数量。该算法不仅能够在限定的时间和内存条件下快速求解,还能够灵活应对动态变化的战场局势,为实时决策提供支持。 ... [详细]
  • 本文详细介绍了 Windows API 中的按钮控件及其应用实例。主要功能包括:1. `CheckDlgButton` 用于更改对话框中按钮的选中状态;2. `CheckRadioButton` 用于设置单选按钮的选中状态。此外,还探讨了按钮控件在实际开发中的多种应用场景,帮助开发者更好地理解和使用这些功能。 ... [详细]
  • HDU1176:免费馅饼问题的动态规划解法分析
    题目“免费馅饼”通过动态规划方法进行了解析。该问题的时间限制为 Java 2000ms 和其他语言 1000ms,内存限制为 Java 65536K 和其他语言 32768K。本文详细探讨了如何利用动态规划算法高效求解此问题,并对算法的时间复杂度和空间复杂度进行了深入分析。此外,还提供了具体的实现步骤和代码示例,帮助读者更好地理解和应用这一方法。 ... [详细]
  • 题目链接:http://codeforces.com/gym/101190/attachments题意:在一个共享三轮车站点,某些用户需要租用车辆。该问题涉及如何通过离线查询和排序优化策略来高效地管理和分配车辆资源。具体来说,需要设计一种算法,在满足所有用户需求的同时,最小化总等待时间和资源浪费。通过合理的数据结构和算法优化,可以显著提高系统的整体性能和用户体验。 ... [详细]
  • 利用树莓派畅享落网电台音乐体验
    最近重新拾起了闲置已久的树莓派,这台小巧的开发板已经沉寂了半年多。上个月闲暇时间较多,我决定将其重新启用。恰逢落网电台进行了改版,回忆起之前在树莓派论坛上看到有人用它来播放豆瓣音乐,便萌生了同样的想法。通过一番调试,终于实现了在树莓派上流畅播放落网电台音乐的功能,带来了全新的音乐享受体验。 ... [详细]
  • 本文全面解析了 gRPC 的基础知识与高级应用,从 helloworld.proto 文件入手,详细阐述了如何定义服务接口。例如,`Greeter` 服务中的 `SayHello` 方法,该方法在客户端和服务器端的消息交互中起到了关键作用。通过实例代码,读者可以深入了解 gRPC 的工作原理及其在实际项目中的应用。 ... [详细]
  • 在Python网络编程中,多线程技术的应用与优化是提升系统性能的关键。线程作为操作系统调度的基本单位,其主要功能是在进程内共享内存空间和资源,实现并行处理任务。当一个进程启动时,操作系统会为其分配内存空间,加载必要的资源和数据,并调度CPU进行执行。每个进程都拥有独立的地址空间,而线程则在此基础上进一步细化了任务的并行处理能力。通过合理设计和优化多线程程序,可以显著提高网络应用的响应速度和处理效率。 ... [详细]
  • 本文深入探讨了 hCalendar 微格式在事件与时间、地点相关活动标记中的应用。作为微格式系列文章的第四篇,前文已分别介绍了 rel 属性用于定义链接关系、XFN 微格式增强链接的人际关系描述以及 hCard 微格式对个人和组织信息的描述。本次将重点解析 hCalendar 如何通过结构化数据标记,提高事件信息的可读性和互操作性。 ... [详细]
  • 如何利用正则表达式(regexp)实现高效的模式匹配?本文探讨了正则表达式在编程中的应用,并分析了一个示例程序中存在的问题。通过具体的代码示例,指出该程序在定义和使用正则表达式时的不当之处,旨在帮助读者更好地理解和应用正则表达式技术。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 【Linux进阶指南】第一阶段第三课:体验与部署Ubuntu系统
    在正式踏上Linux学习之旅之前,本课程将引导你深入体验和部署Ubuntu系统。通过详细的操作步骤和实践演练,你将掌握Ubuntu的基本安装、配置及常用命令,为后续的进阶学习打下坚实的基础。此外,课程还将介绍如何解决常见问题和优化系统性能,帮助你更加高效地使用Ubuntu。 ... [详细]
  • 虚拟机网络设置与数据库远程连接优化指南
    本文针对个人计算机上虚拟机网络配置与数据库远程连接的问题,提供了一套详细的优化指南。在探讨远程数据库访问前,需确保网络配置正确,特别是桥接模式的设置。通过合理的网络配置,可以有效解决因虚拟机或网络问题导致的连接失败,提升远程访问的稳定性和效率。 ... [详细]
  • 为了向用户提供虚拟应用程序,通常会在基础架构中部署StoreFront或Web Interface。为了确保安全的远程访问,通常需要在DMZ中配置Secure Gateway或Access Gateway。本文详细对比了这两种界面工具的功能特性,包括用户管理、安全性、性能优化等方面,为企业选择合适的解决方案提供了全面的参考。 ... [详细]
  • 掌握Linux Shell核心概念与基础技能,本文详细介绍了文件系统和安全管理中的`chmod`命令。`chmod`命令支持两种模式:符号模式和绝对模式。符号模式使用`ugo`表示用户类别,`rwx`表示权限类型;而绝对模式则通过八进制数值来精确设置不同用户的权限。此外,文章还探讨了其他重要的Shell命令和技巧,帮助读者全面理解和应用Linux环境下的文件管理和安全控制。 ... [详细]
  • 题目链接:POJ 2777。问题描述:给定一个区域,需要进行多次涂色操作,并在每次操作后查询某个区间内的不同颜色数量。解决方案:由于题目中颜色种类不超过30种,可以利用线段树的懒惰更新策略来高效处理这些操作。通过懒惰标记,避免了不必要的节点更新,从而显著提高了算法的效率。此外,该方法还能有效应对大规模数据输入,确保在合理的时间内完成所有操作。 ... [详细]
author-avatar
mini泥猴
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有