热门标签 | 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()的使用


推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
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社区 版权所有