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

fork子进程操作共享内存变量test始终输出0的问题求助

解决共享内存变量始终输出0的问题

兄弟,我一眼就看出你这问题出在哪了——典型的进程间竞态条件搞的鬼!咱们一步步拆解:

问题根源

你让父子进程同时对共享内存里的test变量执行「递增→输出→递减→输出」操作,但完全没加任何同步机制。这就好比两个人抢着改同一份文件:父进程刚把test从0加到1,还没来得及打印,子进程就一把抢过来把它减回0;反过来子进程刚递增完,父进程立马给改回去。循环15次下来,每次输出自然都是0。

另外还要确认下你的共享内存初始化和映射是否正确:要是你在fork()之后才创建/映射共享内存,虽然理论上也能访问同一块物理内存,但最好在fork()之前就完成这些操作,避免不必要的坑。

解决方案:用信号量实现互斥同步

咱们用和共享内存配套的System V信号量来做互斥锁,确保同一时间只有一个进程能操作test变量,把你的操作序列变成“原子”执行的。

下面是修改后的完整可运行代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>
#include <wait.h>

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

// P操作:获取信号量(相当于加锁)
void p(int sem_id) {
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = SEM_UNDO;
    semop(sem_id, &sem_op, 1);
}

// V操作:释放信号量(相当于解锁)
void v(int sem_id) {
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = 1;
    sem_op.sem_flg = SEM_UNDO;
    semop(sem_id, &sem_op, 1);
}

int main() {
    // 生成共享内存和信号量的唯一key
    key_t key = ftok(".", 't');
    if (key == -1) {
        perror("ftok failed");
        exit(EXIT_FAILURE);
    }

    // 创建共享内存,大小为int,权限0666(读写权限)
    int shm_id = shmget(key, sizeof(int), IPC_CREAT | 0666);
    if (shm_id == -1) {
        perror("shmget failed");
        exit(EXIT_FAILURE);
    }

    // 映射共享内存到进程地址空间
    int *test = (int *)shmat(shm_id, NULL, 0);
    if (test == (int *)-1) {
        perror("shmat failed");
        exit(EXIT_FAILURE);
    }
    *test = 0; // 初始化变量为0

    // 创建信号量,初始值为1(互斥锁,同一时间只能一个进程持有)
    int sem_id = semget(key, 1, IPC_CREAT | 0666);
    if (sem_id == -1) {
        perror("semget failed");
        exit(EXIT_FAILURE);
    }
    union semun sem_union;
    sem_union.val = 1;
    semctl(sem_id, 0, SETVAL, sem_union);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程逻辑
        for (int i = 0; i < 15; i++) {
            p(sem_id); // 加锁,进入临界区
            (*test)++;
            printf("子进程第%d次:test = %d\n", i+1, *test);
            (*test)--;
            printf("子进程第%d次:test = %d\n", i+1, *test);
            v(sem_id); // 解锁,退出临界区
            sleep(1); // 可选:让输出更清晰,避免刷屏太快
        }
        shmdt(test); // 解除共享内存映射
        exit(EXIT_SUCCESS);
    } else {
        // 父进程逻辑
        for (int i = 0; i < 15; i++) {
            p(sem_id); // 加锁,进入临界区
            (*test)++;
            printf("父进程第%d次:test = %d\n", i+1, *test);
            (*test)--;
            printf("父进程第%d次:test = %d\n", i+1, *test);
            v(sem_id); // 解锁,退出临界区
            sleep(1);
        }
        wait(NULL); // 等待子进程执行完毕
        // 清理资源:解除映射,销毁共享内存和信号量
        shmdt(test);
        shmctl(shm_id, IPC_RMID, NULL);
        semctl(sem_id, 0, IPC_RMID, sem_union);
        exit(EXIT_SUCCESS);
    }
}

额外注意事项

  1. 临界区必须完整:把所有涉及共享变量的操作都包在信号量的P/V操作之间,不能漏。
  2. 资源清理:程序正常退出时一定要销毁共享内存和信号量,不然会残留系统资源。如果程序异常退出,可以用ipcs命令查看残留资源,用ipcrm手动删除。
  3. 其他同步方式:如果不想用信号量,也可以用管道、互斥锁(pthread_mutex_t,但要设置PTHREAD_PROCESS_SHARED属性)来实现同步,不过信号量和System V共享内存是最配套的组合。

现在运行这段代码,你就能看到父子进程分别输出1和0,再也不会出现全0的情况了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:07:10