为何fork多进程中已执行post的共享POSIX信号量仍会造成阻塞?
问题根因
你踩中了POSIX无名信号量跨进程使用的核心误区:sem_init的pshared参数设为非0,仅代表该信号量实现具备跨进程共享的能力,不代表随便放在哪个内存位置都能被不同进程访问。跨进程共享的无名信号量,必须存放在所有关联进程都能共同访问的共享内存区域内,普通全局变量完全不满足这个要求。
你的代码里X、Y、Z都是定义在程序全局数据段的普通变量,fork执行时,子进程会通过写时复制机制生成这三个变量的独立副本,父子进程虽然拿到了数值完全相同的信号量虚拟地址,但实际映射的是互相独立的物理内存页,操作的本质是两个毫无关联的独立信号量,根本无法实现跨进程同步。
对应实际运行现象的逻辑推演
- 父进程完成信号量初始化后执行
fork,父子进程各自持有一套X、Y、Z的内存副本 - 父进程首先执行
sem_post(&Y)、打印60,操作的是父进程自身地址空间内的Y变量,子进程完全感知不到该操作 - 子进程被调度运行后,先给自己地址空间内的X执行
sem_post、打印40,随后执行sem_wait(&Y)——它等待的是自己内存空间里的Y变量,而这个Y从初始化开始就一直是0,没有任何进程能向它发送post操作,因此子进程直接阻塞在这一步 - 父进程打印完60后执行
sem_wait(&X)——它等待的是自己内存空间里的X变量,这个X从初始化开始也一直是0,子进程post的是自己副本里的X,完全触碰不到父进程的X,因此父进程也直接阻塞在这一步
两个进程互相等待对方永远不会发送的信号,程序自然卡死在输出60、40的状态,无法推进到后续逻辑。
修正方法
要实现跨进程共享的无名信号量,必须把信号量实体存放到真正的共享内存区域,比如通过mmap创建父子进程共有的匿名共享内存:
- 不再用全局变量直接定义
sem_t实体,改为定义sem_t类型的指针 - 通过
mmap映射一块共享内存,将信号量指针指向这块共享内存的起始位置 - 所有信号量的初始化、wait、post操作,都针对共享内存上的信号量地址执行
核心代码参考片段:
sem_t *X, *Y, *Z; int main() { // 为每个信号量申请一块父子进程共享的内存 X = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); Y = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); Z = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 在共享内存上初始化信号量,pshared设为1 sem_init(X, 1, 0); sem_init(Y, 1, 0); sem_init(Z, 1, 0); int n = fork(); // 后续所有信号量操作直接用X/Y/Z指针即可,原有业务逻辑保持不变 // ... }
内容的提问来源于stack exchange,提问作者Bilkent Ogrencisi
相关产品推荐
相关产品推荐

