消除基于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; }
关键修改说明:
- 信号竞态修复:
- 用
sigsuspend()替代pause(),配合sigprocmask()提前阻塞SIGUSR1,确保信号仅在等待期间被处理,彻底避免信号丢失。 - 改用
sigaction()配置信号处理,行为跨平台一致性更强。
- 用
- 延迟修正:用
usleep(10000)实现10毫秒精准延迟,避免参数截断问题。 - 字符串安全:给缓冲区预留终止符空间,读取后手动添加
'\0',确保printf输出正常。 - 带宽计算修正:乘以8将字节转换为比特,得到正确的Mbps数值。
- 资源回收:父进程调用
wait(NULL)等待子进程结束,避免僵尸进程产生。
内容的提问来源于stack exchange,提问作者Rico1990
相关产品推荐
相关产品推荐

