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); } }
额外注意事项
- 临界区必须完整:把所有涉及共享变量的操作都包在信号量的P/V操作之间,不能漏。
- 资源清理:程序正常退出时一定要销毁共享内存和信号量,不然会残留系统资源。如果程序异常退出,可以用
ipcs命令查看残留资源,用ipcrm手动删除。 - 其他同步方式:如果不想用信号量,也可以用管道、互斥锁(
pthread_mutex_t,但要设置PTHREAD_PROCESS_SHARED属性)来实现同步,不过信号量和System V共享内存是最配套的组合。
现在运行这段代码,你就能看到父子进程分别输出1和0,再也不会出现全0的情况了!
内容的提问来源于stack exchange,提问作者Curious_guy1111
相关产品推荐
相关产品推荐

