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

多子进程共享变量更新捕获异常:仅单一子进程生效的修复方案

问题根源:无同步机制引发的竞态条件

兄弟,你这是碰到了共享内存编程里的经典坑——竞态条件(Race Condition)!为啥只有第一个子进程的修改能被父进程捕获?本质是多个进程(父+所有子进程)在毫无约束的情况下同时读写共享的标志位c,导致整个操作流程完全乱套:

  • 多个子进程可能同时看到c='n',但只有第一个完成修改的进程能触发父进程的捕获,后续进程的修改会被覆盖或者根本没机会完成;
  • 更糟的是,子进程的“检查c→修改n和pid→设置c='y'”不是原子操作,父进程可能在子进程还没写完n和pid就去读取,或者其他子进程中途打断修改流程。

修复方案:用信号量给共享内存加“互斥锁”

咱们得给共享内存的访问加个“门禁”——用System V信号量(和共享内存配套使用最方便)来实现互斥同步,确保同一时间只有一个进程能进入操作共享变量的临界区。具体分三步:

1. 创建并初始化信号量

  • 建一个包含1个信号量的信号集,初始值设为1(相当于一把可复用的锁,1表示锁可用);
  • 用semop实现P(抢锁,信号量减1)和V(放锁,信号量加1)操作。

2. 临界区强制加锁

  • 不管是父进程还是子进程,在检查/修改共享变量之前必须先抢锁,操作完再放锁;
  • 这样就能保证“检查→修改→设置标志位”的整个流程是原子的,不会被其他进程打断。

3. 清理时别忘了信号量

  • 处理Ctrl-C的信号函数里,除了杀子进程、释放共享内存,还要把信号量也删掉,不然会留在系统里占资源。

完整修复后的代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <signal.h>
#include <time.h>

#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
#define K 3  // 你可以按需修改子进程数量

struct data {
    int pid;
    int n;
    char c;
};

int shmid;
int semid;
struct data *shared_data;
pid_t *child_pids;

// 信号量操作需要的联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P操作:获取锁(信号量减1)
void sem_p() {
    struct sembuf sb = {0, -1, 0};
    semop(semid, &sb, 1);
}

// V操作:释放锁(信号量加1)
void sem_v() {
    struct sembuf sb = {0, 1, 0};
    semop(semid, &sb, 1);
}

// 处理Ctrl-C的清理函数
void sigint_handler(int sig) {
    printf("\n收到终止信号,正在清理所有资源...\n");
    
    // 逐个终止子进程并等待退出
    for (int i = 0; i < K; i++) {
        kill(child_pids[i], SIGTERM);
        waitpid(child_pids[i], NULL, 0);
    }
    
    // 分离并删除共享内存
    shmdt(shared_data);
    shmctl(shmid, IPC_RMID, NULL);
    
    // 删除信号量集
    semctl(semid, 0, IPC_RMID);
    
    free(child_pids);
    exit(0);
}

int main() {
    srand(time(NULL));
    child_pids = malloc(K * sizeof(pid_t));
    if (!child_pids) {
        perror("malloc failed");
        exit(1);
    }
    
    // 创建共享内存
    shmid = shmget(SHM_KEY, sizeof(struct data), IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    shared_data = (struct data *)shmat(shmid, NULL, 0);
    if (shared_data == (void *)-1) {
        perror("shmat failed");
        exit(1);
    }
    
    // 初始化共享变量
    shared_data->c = 'n';
    shared_data->n = 0;
    shared_data->pid = 0;
    
    // 创建信号量并初始化为1(互斥锁初始可用)
    semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget failed");
        exit(1);
    }
    union semun sem_union;
    sem_union.val = 1;
    semctl(semid, 0, SETVAL, sem_union);
    
    // 注册Ctrl-C信号处理
    signal(SIGINT, sigint_handler);
    
    // 创建K个子进程
    for (int i = 0; i < K; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork failed");
            exit(1);
        } else if (pid == 0) {
            // 子进程循环逻辑
            while (1) {
                // 随机休眠0-2秒
                sleep(rand() % 3);
                
                sem_p();  // 进入临界区前抢锁
                if (shared_data->c == 'n') {
                    shared_data->n = rand() % 100;
                    shared_data->pid = getpid();
                    printf("子进程PID %d:设置n为%d\n", getpid(), shared_data->n);
                    shared_data->c = 'y';
                }
                sem_v();  // 退出临界区后放锁
            }
            exit(0);
        } else {
            child_pids[i] = pid;
        }
    }
    
    // 父进程循环逻辑
    while (1) {
        // 随机休眠0-2秒
        sleep(rand() % 3);
        
        sem_p();  // 进入临界区前抢锁
        if (shared_data->c == 'y') {
            printf("父进程捕获:PID %d 修改了n为%d\n", shared_data->pid, shared_data->n);
            shared_data->c = 'n';
        }
        sem_v();  // 退出临界区后放锁
    }
    
    return 0;
}

核心修复点解释
  1. 信号量互斥保护:所有访问共享内存的代码都被sem_p()和sem_v()包裹,彻底杜绝了多个进程同时操作共享变量的情况;
  2. 原子性保障:子进程的修改流程和父进程的捕获流程都变成了原子操作,不会被打断或干扰;
  3. 完整资源清理:新增了信号量的删除操作,避免系统中残留无用的IPC资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 14:37:47