MST
星途 面试题库

面试题:C语言在Linux高并发场景下等待子进程的优化策略

在一个高并发的Linux C语言应用程序中,会频繁创建和等待大量子进程。传统的等待子进程方式可能会导致性能瓶颈,如进程阻塞、资源浪费等问题。请你提出针对这种高并发场景下等待子进程的优化策略,包括但不限于使用信号机制、非阻塞等待方式,并详细阐述每种策略的原理、优缺点以及可能带来的风险。同时给出优化后的代码框架示例。
17.1万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

1. 信号机制

原理:通过注册信号处理函数,当子进程结束时,系统会发送SIGCHLD信号给父进程,父进程的信号处理函数可以在该信号触发时调用waitpid函数来回收子进程资源,而无需父进程一直阻塞等待子进程结束。

优点

  • 非阻塞方式,父进程不会因为等待子进程而阻塞,提高了程序的并发处理能力。
  • 可以异步处理子进程结束事件,在子进程结束时及时回收资源。

缺点

  • 信号处理函数的执行环境比较特殊,编写复杂的逻辑可能存在风险,例如不能在信号处理函数中调用一些不安全的函数。
  • 信号可能会丢失,在高并发场景下如果信号处理不及时,可能会导致部分子进程资源无法及时回收。

风险

  • 由于信号处理函数是异步执行的,可能会与主程序的其他部分产生竞态条件,例如对共享资源的访问冲突。

代码框架示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sigchld_handler(int signum) {
    pid_t pid;
    int status;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        // 处理子进程结束相关逻辑
        printf("Child %d terminated with status %d\n", pid, status);
    }
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    // 主程序创建大量子进程逻辑
    for (int i = 0; i < 10; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程逻辑
            sleep(1);
            exit(i);
        } else if (pid < 0) {
            perror("fork");
            exit(1);
        }
    }

    // 主程序其他逻辑
    while (1) {
        // 主程序继续执行其他任务
        sleep(1);
    }
    return 0;
}

2. 非阻塞等待方式

原理:使用waitpid函数的WNOHANG标志,父进程调用waitpid时不会阻塞,而是立即返回。如果有子进程结束,返回子进程的PID并回收资源;如果没有子进程结束,返回0,父进程可以继续执行其他任务,然后定期调用waitpid检查是否有子进程结束。

优点

  • 父进程不会阻塞等待子进程,保持了程序的并发执行能力。
  • 实现相对简单,不需要额外的信号处理机制。

缺点

  • 父进程需要定期轮询调用waitpid,增加了CPU开销。在高并发场景下,如果轮询频率过高,会消耗大量CPU资源。
  • 可能不能及时回收子进程资源,因为是轮询检查,在两次轮询之间子进程结束可能不能立即处理。

风险

  • 由于轮询频率的不确定性,如果设置不当,可能导致系统资源浪费或者子进程资源回收不及时。

代码框架示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    // 主程序创建大量子进程逻辑
    for (int i = 0; i < 10; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程逻辑
            sleep(1);
            exit(i);
        } else if (pid < 0) {
            perror("fork");
            exit(1);
        }
    }

    // 主程序轮询等待子进程结束
    while (1) {
        pid_t pid;
        int status;
        pid = waitpid(-1, &status, WNOHANG);
        if (pid > 0) {
            // 处理子进程结束相关逻辑
            printf("Child %d terminated with status %d\n", pid, status);
        } else if (pid == 0) {
            // 没有子进程结束,继续执行其他任务
            sleep(1);
        } else {
            // 错误处理
            perror("waitpid");
            break;
        }
    }
    return 0;
}

3. 总结

在高并发场景下等待子进程,信号机制更适合需要及时响应子进程结束事件并且对CPU开销比较敏感的场景,但编写信号处理函数需要更加谨慎;非阻塞等待方式实现简单,但要注意合理设置轮询频率以平衡CPU开销和资源回收及时性。根据具体应用场景的需求,可以选择合适的优化策略或结合使用多种策略。