You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

消除基于Pipe/Fork的进程通信中的潜在竞态条件

解决父子进程交替运行的竞态条件问题

当前代码存在几个核心问题,直接导致程序异常终止或行为不稳定:

  • 信号竞态:子进程调用pause()前,父进程可能已发送SIGUSR1信号,导致信号丢失,子进程进入pause()后永久挂起。
  • sleep参数错误:sleep(0.01)的参数会被截断为0(sleep仅接受整数秒),相当于无延迟,进一步加剧竞态概率。
  • 字符串未终止:tmp1数组未添加'\0'终止符,printf("%s")会读取超出数组范围的内存,引发乱码或未定义行为。
  • 未回收子进程资源:父进程循环结束后直接退出,子进程会成为孤儿进程,且未被回收的子进程会变成僵尸进程。

以下是修复后的代码:

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

#define DATASIZE 5

int count = 0;
double diff = 0;

void wakeup_handler(int sig) {}

int main(int argc, char *argv[])
{
    int pipefd[2];
    pid_t pid;
    int i;

    struct timeval t1, t2;

    if (pipe(pipefd) == -1)
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    // 子进程
    if (pid == 0)
    {
        close(pipefd[0]);

        char tmp1[DATASIZE + 1];
        for (i = 0; i < DATASIZE; i++)
        {
            tmp1[i] = 'a';
        }
        tmp1[DATASIZE] = '\0'; // 添加字符串终止符

        gettimeofday(&t1, NULL);

        // 使用sigaction替代signal,行为更稳定
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = wakeup_handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(SIGUSR1, &sa, NULL) == -1)
        {
            perror("sigaction");
            exit(EXIT_FAILURE);
        }

        sigset_t mask, orig_mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGUSR1);
        // 提前阻塞SIGUSR1,避免信号在sigsuspend前到达
        if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) == -1)
        {
            perror("sigprocmask");
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < 1000; i++)
        {
            write(pipefd[1], tmp1, DATASIZE); // 只写入有效数据
            // 使用sigsuspend等待信号,彻底避免竞态
            if (sigsuspend(&orig_mask) == -1 && errno != EINTR)
            {
                perror("sigsuspend");
                exit(EXIT_FAILURE);
            }
            count++;
            printf("CountC: %d\n", count);
        }

        gettimeofday(&t2, NULL);
        diff = (t2.tv_sec - t1.tv_sec);
        diff += (t2.tv_usec - t1.tv_usec) / 1e6;

        // 修正带宽计算:字节转比特需乘以8
        double band = (DATASIZE * 1000.0 * 8) / diff;
        printf("Average latency: %lf seconds\n", diff / 1000);
        printf("Average bandwidth: %lf Mbps\n", band / 1e6);
        
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }
    // 父进程
    else
    {
        close(pipefd[1]);

        char tmp2[DATASIZE + 1];
        memset(tmp2, 0, sizeof(tmp2)); // 初始化缓冲区

        for (i = 0; i < 1000; i++)
        {
            ssize_t n = read(pipefd[0], tmp2, DATASIZE);
            if (n == -1)
            {
                perror("read");
                exit(EXIT_FAILURE);
            }
            tmp2[n] = '\0'; // 手动添加终止符
            printf("%s\n", tmp2);
            usleep(10000); // 精准等待10毫秒,替代无效的sleep(0.01)
            kill(pid, SIGUSR1);
        }

        close(pipefd[0]);
        wait(NULL); // 等待子进程结束,避免僵尸进程
    }

    return 0;
}

关键修改说明:

  1. 信号竞态修复:
    • 用sigsuspend()替代pause(),配合sigprocmask()提前阻塞SIGUSR1,确保信号仅在等待期间被处理,彻底避免信号丢失。
    • 改用sigaction()配置信号处理,行为跨平台一致性更强。
  2. 延迟修正:用usleep(10000)实现10毫秒精准延迟,避免参数截断问题。
  3. 字符串安全:给缓冲区预留终止符空间,读取后手动添加'\0',确保printf输出正常。
  4. 带宽计算修正:乘以8将字节转换为比特,得到正确的Mbps数值。
  5. 资源回收:父进程调用wait(NULL)等待子进程结束,避免僵尸进程产生。

内容的提问来源于stack exchange,提问作者Rico1990

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 02:50:34