C语言使用Semaphore实现Barrier函数时程序卡死问题排查
屏障实现卡死问题排查与修复
问题背景
尝试实现三个屏障函数:
init_barrier:接收调用屏障的进程数,创建共享内存和信号量,用互斥锁保护共享变量count避免竞态reach_barrier:无参数,遵循给定伪逻辑实现屏障等待destroy_barrier:仅父进程调用时销毁信号量、清理共享内存
给定伪代码:
int nproc = 0, count = 0; sem_t barrier; // Initializes the barrier void init_barrier(int num_proc) { nproc = num_proc; count = 0; Initialize barrier to 0. } // Every process calls this to “reach” the barrier void reach_barrier() { count++; if(count == nproc) { // Release one process signal(barrier); } else { // We are not the last process. So we wait at the // semaphore until we are freed. wait(barrier); // Now that we are freed, we free the next process signal(barrier); } }
用户实现代码(原错误版本):
#include <stdio.h> #include <stdlib.h> #include <semaphore.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include "barrier.h" int nproc = 0, count = 0; sem_t barrier; // Semaphore for the barrier sem_t mutex; // Semaphore to protect the 'count' variable; void init_barrier(int num_proc) { nproc = num_proc; count = 0; // Initialize the barrier semaphore to 0 if (sem_init(&barrier, 0, 0) != 0) { perror("Failed to initialize barrier semaphore"); exit(EXIT_FAILURE); } // Initialize the mutex semaphore to 1 if (sem_init(&mutex, 0, 1) != 0) { perror("Failed to initialize mutex semaphore"); exit(EXIT_FAILURE); } } void reach_barrier() { sem_wait(&mutex); // Lock the mutex to protect 'count' count++; if (count == nproc) { // Release all waiting processes int i; for (i = 0; i < nproc - 1; i++) { sem_post(&barrier); } } else { // Release the mutex before waiting sem_post(&mutex); // Wait at the barrier until released sem_wait(&barrier); } sem_wait(&mutex); // Lock the mutex to protect 'count' count--; if (count == 0) { // The last process releases the waiting processes int i; for (i = 0; i < nproc - 1; i++) { sem_post(&barrier); } } sem_post(&mutex); // Unlock the mutex } void destroy_barrier(int my_pid) { if (my_pid == 0) { // This is the parent process // Destroy semaphores if (sem_destroy(&barrier)) { perror("Failed to destroy barrier semaphore"); exit(EXIT_FAILURE); } if (sem_destroy(&mutex) != 0) { perror("Failed to destroy mutex semaphore"); exit(EXIT_FAILURE); } // Detach and free any shared memory if needed // You can add code here to handle shared memory cleanup } }
测试代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <sys/wait.h> #include "barrier.h" #define NUM_PROCESSES 6 #define MAX_SLEEP 1000000 // Maximum sleep period in microseconds int main() { int i, pid; int sleep_time[NUM_PROCESSES]; init_barrier(NUM_PROCESSES+1); srand(time(NULL)); for(i=0; i<NUM_PROCESSES; i++) { // The children will all sleep for different amounts of time sleep_time[i] = (int) (((float) rand() / RAND_MAX) * MAX_SLEEP); if((pid = fork()) == 0) { srand(time(NULL)); break; } } if(pid == 0) { usleep(sleep_time[i]); // Reach the barrier. printf(" Child %d slept for %3.2f seconds and has now reached the barrier\n", i, sleep_time[i]/1000000.0); reach_barrier(); } else { // Parent will just wait at barrier for all children to return printf("**Parent waiting for children**\n\n"); reach_barrier(); printf("\n**All the children have returned**\n"); // Clean up the process table for(i=0; i<NUM_PROCESSES; i++) wait(NULL); destroy_barrier(pid); } }
运行现象:程序卡死,无法打印**All the children have returned**
核心错误分析
信号量与全局变量未实现进程共享
sem_init的第二个参数pshared设为0,意味着信号量仅在当前进程的线程间共享,fork后的子进程会拷贝这些变量的副本,父进程和子进程的count、barrier、mutex完全独立,根本无法实现同步。- 全局变量
nproc、count也需要放在共享内存中,否则子进程修改的是自己的副本,父进程无法感知。
reach_barrier逻辑冗余错误- 原伪代码仅在第一次计数到
nproc时放行,而用户额外添加了count--和第二次sem_post循环,这会导致信号量被错误地多次post,引发额外的等待或唤醒逻辑混乱。
- 原伪代码仅在第一次计数到
destroy_barrier父进程判断错误- fork后父进程的
pid是子进程的ID(非0),子进程的pid是0,所以if(my_pid ==0)的判断完全错误,父进程永远不会执行销毁逻辑。
- fork后父进程的
修复后的完整实现
barrier.h
#ifndef BARRIER_H #define BARRIER_H #include <semaphore.h> // 共享内存结构体,存放需要跨进程共享的变量 typedef struct { int nproc; int count; sem_t barrier; sem_t mutex; } BarrierShared; extern BarrierShared *barrier_share; void init_barrier(int num_proc); void reach_barrier(); void destroy_barrier(); #endif
barrier.c
#include <stdio.h> #include <stdlib.h> #include <semaphore.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h> #include "barrier.h" BarrierShared *barrier_share; static int shmid; void init_barrier(int num_proc) { // 创建共享内存,大小为BarrierShared结构体 shmid = shmget(IPC_PRIVATE, sizeof(BarrierShared), IPC_CREAT | 0666); if (shmid == -1) { perror("shmget failed"); exit(EXIT_FAILURE); } // 将共享内存映射到当前进程地址空间 barrier_share = (BarrierShared *)shmat(shmid, NULL, 0); if (barrier_share == (void *)-1) { perror("shmat failed"); exit(EXIT_FAILURE); } // 初始化共享变量 barrier_share->nproc = num_proc; barrier_share->count = 0; // 初始化屏障信号量,pshared=1表示进程间共享,初始值0 if (sem_init(&barrier_share->barrier, 1, 0) != 0) { perror("Failed to initialize barrier semaphore"); exit(EXIT_FAILURE); } // 初始化互斥信号量,pshared=1,初始值1 if (sem_init(&barrier_share->mutex, 1, 1) != 0) { perror("Failed to initialize mutex semaphore"); exit(EXIT_FAILURE); } } void reach_barrier() { sem_wait(&barrier_share->mutex); // 加锁保护count barrier_share->count++; if (barrier_share->count == barrier_share->nproc) { // 最后一个到达的进程,唤醒所有等待的进程 for (int i = 0; i < barrier_share->nproc - 1; i++) { sem_post(&barrier_share->barrier); } // 最后一个进程不需要等待,直接释放锁 sem_post(&barrier_share->mutex); } else { // 释放锁后等待 sem_post(&barrier_share->mutex); sem_wait(&barrier_share->barrier); } } void destroy_barrier() { // 只有初始父进程执行销毁 static pid_t parent_pid = 0; if (parent_pid == 0) { parent_pid = getpid(); } if (getpid() != parent_pid) { return; } // 销毁信号量 if (sem_destroy(&barrier_share->barrier) != 0) { perror("Failed to destroy barrier semaphore"); exit(EXIT_FAILURE); } if (sem_destroy(&barrier_share->mutex) != 0) { perror("Failed to destroy mutex semaphore"); exit(EXIT_FAILURE); } // 分离共享内存 if (shmdt(barrier_share) != 0) { perror("shmdt failed"); exit(EXIT_FAILURE); } // 删除共享内存 if (shmctl(shmid, IPC_RMID, NULL) != 0) { perror("shmctl IPC_RMID failed"); exit(EXIT_FAILURE); } }
测试代码修改
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <sys/wait.h> #include "barrier.h" #define NUM_PROCESSES 6 #define MAX_SLEEP 1000000 // Maximum sleep period in microseconds int main() { int i, pid; int sleep_time[NUM_PROCESSES]; // 总进程数是子进程数+父进程,共7个 init_barrier(NUM_PROCESSES + 1); srand(time(NULL)); for(i=0; i<NUM_PROCESSES; i++) { sleep_time[i] = (int) (((float) rand() / RAND_MAX) * MAX_SLEEP); if((pid = fork()) == 0) { // 子进程重新设置随机种子,避免和父进程重复 srand(time(NULL) ^ getpid()); break; } } if(pid == 0) { usleep(sleep_time[i]); printf(" Child %d slept for %3.2f seconds and has now reached the barrier\n", i, (double)sleep_time[i]/1000000.0); reach_barrier(); // 子进程退出前分离共享内存 shmdt(barrier_share); exit(EXIT_SUCCESS); } else { printf("**Parent waiting for children**\n\n"); reach_barrier(); printf("\n**All the children have returned**\n"); // 等待所有子进程结束 for(i=0; i<NUM_PROCESSES; i++) wait(NULL); destroy_barrier(); } return 0; }
修复说明
- 进程间共享实现:用
shmget创建共享内存,将nproc、count和两个信号量放入共享结构体,sem_init的pshared设为1,确保跨进程可见。 - 简化
reach_barrier逻辑:移除多余的count--和第二次post循环,严格遵循原伪代码逻辑,仅在最后一个进程到达时唤醒所有等待者。 - 修正父进程判断:用
getpid()记录初始父进程ID,确保只有父进程执行销毁逻辑。 - 子进程资源清理:子进程退出前分离共享内存,避免资源泄漏。
编译运行后,程序将正常打印所有子进程到达屏障的信息,最后输出**All the children have returned**,无卡死现象。
内容的提问来源于stack exchange,提问作者anonymous
相关产品推荐
相关产品推荐

