C语言中Fork多进程共享内存计数数组的并发安全问题及互斥锁实现咨询
关于多进程共享内存计数的线程安全问题与解决方案
好问题!你提到的直接用共享内存存储count数组的方案完全不安全,因为多个进程同时对数组元素执行count[output]++操作会触发竞态条件(race condition),导致计数结果错误。
为什么不安全?
count[output]++看起来是一个单一操作,但实际上它在底层会被拆分成三个独立的CPU指令:
- 从内存中读取
count[output]的当前值到寄存器 - 将寄存器中的值加1
- 把新值写回内存
如果两个进程同时对同一个元素执行这三步操作,就可能出现“覆盖”问题:比如进程A刚读完值还没写回,进程B也读了同一个旧值,两者加1后写回,最终结果只增加了1,而不是正确的2。
如何实现安全的互斥访问?
你需要用进程间同步原语来保证同一时间只有一个进程能修改count数组。最常用的两种方案是POSIX互斥锁(进程共享型)和信号量。下面以POSIX互斥锁为例,给出完整的实现思路和代码示例:
步骤1:定义共享内存结构
我们需要把count数组和互斥锁放在同一块共享内存中,这样所有子进程都能访问到锁:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include <sys/wait.h> #define SHM_NAME "/count_shm" #define N 10 // 对应你的集合{0,...,n-1}的大小n // 共享内存中的结构体,包含计数数组和互斥锁 typedef struct { pthread_mutex_t mutex; int count[N]; } SharedData;
步骤2:创建/初始化共享内存与互斥锁
父进程负责创建共享内存,并初始化互斥锁和count数组:
int main() { int shm_fd; SharedData *shared_data; // 创建共享内存对象 shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } // 设置共享内存大小 if (ftruncate(shm_fd, sizeof(SharedData)) == -1) { perror("ftruncate failed"); exit(EXIT_FAILURE); } // 映射共享内存到进程地址空间 shared_data = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); if (shared_data == MAP_FAILED) { perror("mmap failed"); exit(EXIT_FAILURE); } // 初始化互斥锁的进程共享属性 pthread_mutexattr_t mutex_attr; pthread_mutexattr_init(&mutex_attr); pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&shared_data->mutex, &mutex_attr); pthread_mutexattr_destroy(&mutex_attr); // 初始化count数组为0 for (int i = 0; i < N; i++) { shared_data->count[i] = 0; }
步骤3:Fork子进程并分发任务
每个子进程在修改count数组前,先加锁,修改完成后解锁:
// 假设我们创建4个子进程(根据CPU核心数调整) const int num_processes = 4; int total_tasks = 1000; // 总任务数 int tasks_per_process = total_tasks / num_processes; for (int p = 0; p < num_processes; p++) { pid_t pid = fork(); if (pid == -1) { perror("fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程逻辑 int start = p * tasks_per_process; int end = (p == num_processes - 1) ? total_tasks : (p + 1) * tasks_per_process; for (int i = start; i < end; i++) { int input = generate_input(i); // 你的生成输入函数 int output = the_computation(input); // 你的计算函数 // 加锁,保证互斥访问count数组 pthread_mutex_lock(&shared_data->mutex); shared_data->count[output]++; pthread_mutex_unlock(&shared_data->mutex); } // 子进程退出前解除内存映射 munmap(shared_data, sizeof(SharedData)); close(shm_fd); exit(EXIT_SUCCESS); } }
步骤4:父进程等待子进程并清理资源
// 等待所有子进程完成 for (int p = 0; p < num_processes; p++) { wait(NULL); } // 输出最终计数结果 printf("最终计数结果:\n"); for (int i = 0; i < N; i++) { printf("count[%d] = %d\n", i, shared_data->count[i]); } // 销毁互斥锁,释放共享内存 pthread_mutex_destroy(&shared_data->mutex); munmap(shared_data, sizeof(SharedData)); close(shm_fd); shm_unlink(SHM_NAME); return 0; } // 示例的generate_input和the_computation函数(根据你的实际需求替换) int generate_input(int i) { return i; } int the_computation(int input) { return input % N; // 示例:输出为input模N的结果 }
关键注意事项
- 互斥锁必须设置
PTHREAD_PROCESS_SHARED属性,否则只能在同一进程的线程间共享,无法跨进程使用。 - 所有进程在使用完共享内存后,都要调用
munmap解除映射,父进程最后要调用shm_unlink删除共享内存对象,避免系统资源泄漏。 - 如果你的系统不支持POSIX共享内存(比如某些嵌入式系统),可以改用System V共享内存(
shmget、shmat等接口),配合System V信号量实现互斥。
内容的提问来源于stack exchange,提问作者optical
相关产品推荐
相关产品推荐

