【问题标题】:fork 100 processes at same time and sometimes some processes become zombie同时 fork 100 个进程,有时某些进程会变成僵尸进程
【发布时间】:2017-08-02 07:20:28
【问题描述】:

我尝试在以下代码中同时启动100个进程:

int cnt = 0;

void sig_handler(int signo) {
    pid_t pid;
    int stat;
    pid = wait(&stat);
    cout << "cnt:" << ++cnt << ", pid:" << pid << " signal:" << signo << endl;
}

int main() {
    signal(SIGCHLD, sig_handler);
    for (int i = 0; i < 100; ++i) {
        if (fork() == 0) {
            sleep(1);
            exit(0);
        }
    }
    printf("wait\n");
    while (1);
}

我在sig_handler 中捕捉到SIGCHLD 信号,结果不同:有时所有进程都返回OK;有时 1 到 4 个进程会变成僵尸。

[vinllen@my-host]$ ./a.out
wait
cnt:1, pid:4383 signal:17
cnt:2, pid:4384 signal:17
cnt:3, pid:4385 signal:17
cnt:4, pid:4386 signal:17
cnt:5, pid:4387 signal:17
…
cnt:94, pid:4476 signal:17
cnt:95, pid:4477 signal:17
cnt:96, pid:4478 signal:17
cnt:97, pid:4479 signal:17
cnt:98, pid:4480 signal:17

[vinllen@my-host ~]$ ps aux | grep a.out
Vinllen       4382 96.2  0.0  13896  1084 pts/8    R+   15:14   0:03 ./a.out
Vinllen       4481  0.0  0.0      0     0 pts/8    Z+   15:14   0:00 [a.out] <defunct>
Vinllen       4482  0.0  0.0      0     0 pts/8    Z+   15:14   0:00 [a.out] <defunct>
Vinllen       4493  0.0  0.0 105300   864 pts/9    S+   15:14   0:00 grep a.out

我猜原因是多个进程同时退出并触发了某些东西。谁能给我详细的原因并告诉我如何解决这个问题。

在我的理解中,双叉和忽略 SIGCHLD 是解决这个问题的两种有效方法。但是,如何在这段代码中解决仍然调用wait

【问题讨论】:

  • 信号处理程序中的 cout 可能是罪魁祸首。你应该只在信号处理程序中调用async-signal-safe functions。一般来说,尽量缩短信号处理程序,并通过volatile sig_atomic_t 变量与主代码进行通信。

标签: c++ linux fork zombie-process waitpid


【解决方案1】:

信号没有排队。如果一个 SIGCHLD 在一个挂起时引发(可能是当您的代码在 write 系统调用中时),程序将只收到一个通知。

处理这个问题的正确方法是在你的处理程序中循环,直到所有完成的孩子都被收割:

void sig_handler(int signo) {
    pid_t pid;
    int stat;
    while ((pid = waitpid(-1, &stat, WNOHANG) > 0)
    if (WIFEXITED(stat))
    {
        // Don't actually do this: you should
        // avoid buffered I/O in signal handlers.
        std::cout << "count:" << ++cnt
                  << ", pid:" << pid
                  << " signal:" << signo
                  << std::endl;
    }
}

如 cmets 中所述,您应该坚持使用信号处理程序中记录的 async-signal-safe functions。缓冲 I/O(包括使用 std::cout)可能有风险,因为信号处理程序可以在操作其内部结构时被调用。避免问题的最佳方法是限制自己使用volatile sig_atomic_t 变量与主代码进行通信。

【讨论】:

  • 我确信这是正确的答案,因此赞成,但我仍然很确定在信号处理程序中使用 cout 与使用 C stdio 函数一样不安全,所以这里有一个 潜在的如果主代码也在做一些 I/O 的话……整个收获和打印循环应该移到处理程序之外。
  • 基于此我还有一个问题,哪个进程调用sig_handler,父进程?还是子进程?还是父进程中的新线程?
  • @Felix: write 列在安全功能列表中。 std::cout 所做的任何其他事情(维护状态等)都受到 STL 实现的通常可重入性约束。
  • @vinllen 不,信号处理程序与线程无关。如果有多个线程(在您的代码中不是这种情况),则未指定 which 线程将运行处理程序。信号处理程序中断主代码。
  • @vinllen,如果有一个线程,就像在这段代码中一样,那就是将被中断以运行信号处理程序的线程。当处理程序返回时它将恢复。 Unix 信号早于线程,因此不要假设线程可用。
猜你喜欢
  • 2023-03-20
  • 2016-07-03
  • 2011-01-26
  • 1970-01-01
  • 1970-01-01
  • 2014-01-08
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多