ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

共享内存 多进程并发竞争:非原子操作引发数据丢失

共享内存 多进程并发竞争:非原子操作引发数据丢失 一、基础概念1. 共享内存共享内存是 Linux 常用 IPC 通信方式由内核开辟一块独立物理内存允许多个进程同时映射、读写同一份数据是多进程数据共享的核心方案。2. 非原子操作与竞态条件原子操作一次性执行完成中途不可被系统打断。自增 不是原子操作底层拆分为三步独立 CPU 指令1. 读从内存读取数值到寄存器 2. 改寄存器数值 1 3. 写新值写回内存系统可在三步之间切换进程多进程并行操作会出现指令交错、数据覆盖产生竞态条件最终导致数据丢失。二、完整代码小白友好注释#include sys/shm.h #include sys/ipc.h #include stdio.h #include unistd.h #include sys/wait.h // 用于生成唯一IPC键值 #define SHARE_PATH /home/china #define PROJ_ID 251 int main() { // 1. 生成IPC唯一键值 key_t key ftok(SHARE_PATH, PROJ_ID); if (key -1) { perror(ftok); return -1; } // 2. 创建1000字节共享内存 int shmid shmget(key, 1000, IPC_CREAT | 0666); if (shmid -1) { perror(shmget); return -1; } // 3. 映射共享内存到当前进程虚拟空间 int *p (int *)shmat(shmid, NULL, 0); if (p (void *)-1) { perror(shmat); return -1; } *p 0; // 初始化共享计数器 // 4. 创建父子进程并发执行 pid_t pid fork(); if (pid 0) { // 子进程自增100万次 int i 0; while (i 1000000) (*p); } else if (pid 0) { // 父进程自增100万次 int i 0; while (i 1000000) (*p); wait(NULL); // 等待子进程结束 printf(最终计数 data %d\n, *p); } shmdt(p); // 解除映射 shmctl(shmid, IPC_RMID, NULL); // 主动销毁共享内存避免内核残留 return 0; }三、运行效果理论值2000000父子各累加100万次实际值随机数值始终小于 2000000现象并发无同步保护出现大量计数丢失。四、原理详解1. 为什么会丢数据自增操作分三步系统可以随时切进程导致两个进程读到同一个旧值父进程读取数值 100还没写回被系统暂停子进程同样读取到旧值 100子进程算 1001101写回内存切回父进程父进程也算出 101再次覆盖写回。结果父进程两次自增只加了1次数据丢失一次。循环一百万次这种覆盖反复发生最终数值远远小于理论值。同步锁的作用保证「读-改-写」三步一次性做完中途不允许其他进程插队。补充循环设为 100 万而非 10 万延长运行时间提升进程切换概率更容易复现数据丢失。如果只循环 10 万次程序运行太快系统来不及切换进程大概率看不到丢数现象2. 为什么不用全局变量fork 后父子进程虚拟地址空间相互独立内核采用写时拷贝 COW机制初始父子共享同一块物理内存一旦任意进程修改全局变量内核会为该进程复制一份全新物理内存。修改后父子数据彻底隔离无法共享计数因此必须使用共享内存。五、总结1. 普通全局变量受 COW 机制隔离多进程共享数据只能使用共享内存2.是非原子操作读改写拆分是并发数据丢失的根本原因3. 多进程并发写共享资源必须加同步机制互斥锁/信号量保护临界区4. 程序结束需调用shmctl(IPC_RMID)主动释放共享内存资源。六、运行须知1. 提前执行mkdir /home/china防止 ftok 报错2. 异常退出可手动清理残留ipcs -m查看、ipcrm -m 编号删除。
返回列表