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

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**


核心错误分析

  1. 信号量与全局变量未实现进程共享

    • sem_init的第二个参数pshared设为0,意味着信号量仅在当前进程的线程间共享,fork后的子进程会拷贝这些变量的副本,父进程和子进程的count、barrier、mutex完全独立,根本无法实现同步。
    • 全局变量nproc、count也需要放在共享内存中,否则子进程修改的是自己的副本,父进程无法感知。
  2. reach_barrier逻辑冗余错误

    • 原伪代码仅在第一次计数到nproc时放行,而用户额外添加了count--和第二次sem_post循环,这会导致信号量被错误地多次post,引发额外的等待或唤醒逻辑混乱。
  3. destroy_barrier父进程判断错误

    • fork后父进程的pid是子进程的ID(非0),子进程的pid是0,所以if(my_pid ==0)的判断完全错误,父进程永远不会执行销毁逻辑。

修复后的完整实现

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;
}

修复说明

  1. 进程间共享实现:用shmget创建共享内存,将nproc、count和两个信号量放入共享结构体,sem_init的pshared设为1,确保跨进程可见。
  2. 简化reach_barrier逻辑:移除多余的count--和第二次post循环,严格遵循原伪代码逻辑,仅在最后一个进程到达时唤醒所有等待者。
  3. 修正父进程判断:用getpid()记录初始父进程ID,确保只有父进程执行销毁逻辑。
  4. 子进程资源清理:子进程退出前分离共享内存,避免资源泄漏。

编译运行后,程序将正常打印所有子进程到达屏障的信息,最后输出**All the children have returned**,无卡死现象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:42:36