System V IPC实战:共享内存、消息队列与信号量详解

发布时间:2026/10/13 3:45:34
System V IPC实战:共享内存、消息队列与信号量详解 1. 从一次进程通信的堵塞开始的为什么要重学System V IPC大约两年多前我在做模拟项目X一个用C语言写的数据分发服务。服务里跑着三个进程进程A负责从数据源持续读取原始数据进程B接收数据做聚合计算进程C负责把结果落盘并生成索引。最开始版本是单进程单线程全局变量加锁就够了。业务量上来以后架构拆成多个进程并行麻烦立刻出现了——fork之后父子进程的内存互相独立原来的“全局变量锁”彻底失效进程间根本看不到彼此改了什么。当时组里的应急方案是用临时文件传数据每秒创建一个新文件让另一个进程去读。结果磁盘IO直接被打满数据延迟从毫秒级飙到秒级CPU空转严重业务方天天来催。复盘的时候一位老工程师翻着《Unix环境高级编程》说了一句“你们这个场景根本不需要碰磁盘System V IPC就是干这个的。”我第一次认真接触System V IPC说实话一开始很受挫。ftok、shmget、semop、msgrcv这些函数名又长又像参数多man page还写得晦涩。再加上信号量、消息队列、共享内存三个机制经常被混在一起讲很容易一头雾水。真正把内核里那套“公共资源池”的模型想明白之后才发现它其实非常朴素内核开辟几块公共区域进程通过系统调用读写并不比读写文件复杂多少。我们自己之前绕了一大圈磁盘反而是走了远路。这篇文章想把我从“被System V IPC劝退”到“能熟练组织多进程协作”的过程完整整理一遍。核心围绕四个主题ftok和key的来龙去脉、共享内存、消息队列、信号量。我会讲清楚每个机制怎么用、底层原理是什么、实际项目中会遇到哪些坑并且给出可复现的代码片段。如果你现在正在写Linux下的C/C程序或者需要在项目里做多进程协作这篇文章应该能帮你省下不少摸索时间。2. System V IPC 的核心概念key、标识符和内核对象2.1 内核里的“公共资源池”System V IPC的模型可以想象成内核里有一块公共资源池。进程A往池里放数据进程B从池里取数据。这块池子不属于任何一个进程私有只要系统不重启池里数据就一直存在。这和管道的模型完全是两回事。管道是面向流的、一对一的数据读走就没了像一根一次性吸管。System V IPC面向结构可以反复存取而且支持多个进程同时访问同一个对象。这种差异决定了它们适用的场景天然不同。System V IPC具体包含三种机制共享内存内核映射一块物理内存多个进程把它挂载到自己的地址空间直接读写这块内存。消息队列内核维护一个带类型的消息链表进程按类型发送、接收消息消息自带类型字段不强制先进先出。信号量内核维护一组计数器用来做进程间的同步与互斥控制它本身不传递数据。这三种机制解决的问题各有侧重但在内核层面都遵循同一套“创建/获取-操作-控制”的套路创建/获取用xxxget()系列函数拿到一个IPC标识符identifier。操作用xxxop()系列函数执行具体动作比如读写共享内存、发送/接收消息、P/V操作信号量。控制用xxxctl()系列函数设置参数、查询状态或删除对象。记住了这个套路再去看shmget、msgget、semget三个函数的记忆成本就低了很多。它们的第一个参数都是key最后一个参数都是flags中间参数各不相同但整体结构高度相似。2.2 keyIPC世界的“门牌号”我最初最不理解的就是key。信号量有semget第一参数要求给一个key_t类型的key。这个key到底是什么和文件描述符有什么关系简化理解key是用户空间用来描述“我要找哪块内核资源”的全局编号xxxget返回的IPC标识符则是内核分配给具体对象的“本地句柄”只能在这个进程内部使用。打个比方key是门牌号标识符是开门用的钥匙。你按门牌号找到那扇门拿到钥匙之后才能进去操作。标准做法是通过ftok函数生成key#include sys/types.h #include sys/ipc.h key_t ftok(const char *pathname, int proj_id);ftok接受一个已存在的路径名和一个项目ID内部把路径名的inode编号和proj_id组合成一个key_t值。多个进程使用相同的pathname和proj_id得到的key就一样因此能定位到同一个IPC对象。这里有几个实际踩过的坑值得注意路径必须真实存在且当前进程要有访问权限。否则ftok返回-1后面所有调用全部失败排查起来很头大。proj_id可以填一个字符或小整数但别所有项目都用同一个。不同项目用了同一个路径和同一个proj_idkey就会冲突进程可能拿到别人家的共享内存。同一个路径同一个proj_id在系统上生成的key是固定的因为inode编号短时期内不会变。2.3 IPC_PRIVATE 的两个适用边界很多教程会推荐这样创建int shmid shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);IPC_PRIVATE的值是0含义是“不用key让内核自动分配一个全新IPC对象”。这本身是合法用法一般用于父子进程之间的通信。因为fork出来的子进程会继承父进程的内存映像shmid这个变量的值子进程天然就知道不需要跨进程传递key。但如果你写的是多个无亲缘关系进程协作的程序用IPC_PRIVATE就是灾难另一个进程根本没有办法知道shmid是多少。没有门牌号自然找不到门。我在项目里就遇到过同事实名翻车——一个进程用IPC_PRIVATE创建了消息队列另一个进程拿着ftok生成的key去msgget怎么都拿不到同一个队列。最后老老实实统一走ftok。2.4 查看和清理IPC对象ipcs 与 ipcrm调试System V IPC时最常用的命令行工具是ipcs和ipcrm命令作用ipcs -m查看共享内存ipcs -q查看消息队列ipcs -s查看信号量ipcs -a查看全部System V IPC对象ipcrm -m shmid删除指定共享内存ipcrm -q msqid删除指定消息队列ipcrm -s semid删除指定信号量如果程序异常退出或者忘记调用删除函数IPC对象会一直残留在系统里。我调试时经常是先跑一遍程序再用ipcs -a看有没有“尸体重叠”确认之后再手动清。开发阶段偶尔可以ipcrm -a一把梭生产环境千万别随手执行你根本不知道哪个服务正占着某个信号量在跑业务。注意很多新人疑惑“我的程序明明调了IPC_RMID为什么ipcs里还看得到”这多半是还有进程仍然挂载或引用着这块资源。共享内存的删除是延迟的等最后一个进程shmdt之后才真正释放。3. 共享内存最快但最需要自律的通信方式3.1 共享内存为什么快共享内存的原理一句话就能讲清内核把同一块物理内存映射到多个进程的虚拟地址空间。进程A写到这个地址的数据进程B在读同一地址时直接就能看到完全不需要经过内核拷贝。对比一下消息队列发一条消息要从用户空间复制到内核空间接收时再从内核空间复制回用户空间两次拷贝开销不小。共享内存只要第一次建立映射后续读写就是普通内存访问速度几乎等同于进程内部的指针操作。所以共享内存是三种System V IPC里吞吐量最大、效率最高的方案典型场景是大批量数据的传输。但这块“快”是有代价的共享内存本身不提供任何同步保护。多个进程同时读写必须自己控制互斥和顺序。它就像一间所有人都能进去写字的房间门口没人看着谁进谁出全靠自觉。这也是它被称为“最需要自律”的原因。3.2 第一步用 shmget 创建或获取共享内存#include sys/shm.h int shmget(key_t key, size_t size, int shmflg);参数含义key门牌号用来定位或创建共享内存。size共享内存大小单位字节。内核实际分配按页对齐通常一页4096字节。你传100可能占用4096但能访问的空间仍然是size规定的范围。shmflg权限标志通常是IPC_CREAT | 0666。如果加IPC_EXCL当key对应的对象已存在时返回失败可以用来判断当前进程是创建者还是连接者。实际开发中用IPC_CREAT | IPC_EXCL判定身份非常有用。创建者负责初始化数据和信号量连接者只负责连接两边职责分开能避免重复初始化。我这里还见过一个非常典型的错误创建时传size 4096获取方传size 8192两边映射大小不一致。写入方只初始化了前4096字节读取方按8192去读后半段全是未定义内容。排查了一个多小时才发现最后统一用一个宏定义任何进程都不得单独改。3.3 第二步用 shmat 把共享内存挂到进程地址空间void *shmat(int shmid, const void *shmaddr, int shmflg);shmaddr通常传NULL让内核自己选一个合适的虚拟地址。shmflg一般传0如果只想只读挂载传SHM_RDONLY。shmat返回挂载后的虚拟地址失败时返回(void *)-1。我看到有人直接用(void *)0判断失败这是错的。虽然实际中NULL附近的地址极少作为合法映射但严谨的判断应该是void *addr shmat(shmid, NULL, 0); if (addr (void *)-1) { perror(shmat); return -1; }shmaddr原则上可以手动指定但我极其不建议。之前一个实验版本里有人为了“对齐到固定地址”硬把共享内存挂到某个固定虚拟地址结果跟动态库加载地址撞了程序直接段错误。让内核自动选择最安全。3.4 第三步shmdt 解除挂载shmctl 删除对象解除挂载int shmdt(const void *shmaddr);删除对象int shmctl(int shmid, IPC_RMID, NULL);两个细节非常关键shmdt只是解除当前进程的映射不会删除共享内存对象。删除必须单独调用IPC_RMID。IPC_RMID有延迟删除特性如果还有进程正在挂载这块内存IPC_RMID不会立刻回收物理内存等最后一个进程shmdt之后才真正释放。这个设计很聪明能避免“进程A删了、进程B还在用”导致的崩溃但反过来也容易让人误判——你以为删了ipcs -m里却还在稍微一查才发现是某个连接进程还没退出。3.5 共享内存不提供同步怎么办共享内存是一块裸内存多个进程同时读写时必须自己加同步控制。最常规的方案就是配合信号量一个信号量管“数据是否就绪”一个信号量管“缓冲区是否有空位”。在模拟项目X里我们就是这样设计的。生产者写共享内存之前先P(空位)写完以后V(数据就绪)消费者读之前先P(数据就绪)读完之后V(空位)。两边都能保证消费者不会读到写了一半的数据。生产者不会覆盖尚未被消费的数据。3.6 共享内存 信号量代码框架下面这段代码是模拟项目X里用过的简化版框架只贴核心逻辑演示生产者的写入端/* producer.c —— 共享内存写入端 */ #include stdio.h #include stdlib.h #include string.h #include sys/ipc.h #include sys/shm.h #include sys/sem.h #include sys/types.h #include unistd.h #define SHM_SIZE 4096 #define SEM_READY 0 /* 数据就绪信号量 */ #define SEM_EMPTY 1 /* 缓冲区空闲信号量 */ union semun { int val; struct semid_ds *buf; unsigned short *array; }; void P(int semid, int idx) { struct sembuf op {idx, -1, 0}; if (semop(semid, op, 1) -1) { perror(semop P); exit(1); } } void V(int semid, int idx) { struct sembuf op {idx, 1, 0}; if (semop(semid, op, 1) -1) { perror(semop V); exit(1); } } int main() { key_t key ftok(/tmp/shm_demo, x); if (key (key_t)-1) { perror(ftok); exit(1); } int shmid shmget(key, SHM_SIZE, IPC_CREAT | 0666); if (shmid -1) { perror(shmget); exit(1); } int semid semget(key, 2, IPC_CREAT | 0666); if (semid -1) { perror(semget); exit(1); } char *addr shmat(shmid, NULL, 0); if (addr (void *)-1) { perror(shmat); exit(1); } /* 初始化信号量缓冲区空闲1数据就绪0 */ union semun su; su.val 1; if (semctl(semid, SEM_EMPTY, SETVAL, su) -1) { perror(semctl EMPTY); exit(1); } su.val 0; if (semctl(semid, SEM_READY, SETVAL, su) -1) { perror(semctl READY); exit(1); } for (int i 0; i 5; i) { P(semid, SEM_EMPTY); snprintf(addr, SHM_SIZE, hello from producer, round %d, i); V(semid, SEM_READY); printf(producer write round %d\n, i); sleep(1); } shmdt(addr); shmctl(shmid, IPC_RMID, NULL); semctl(semid, 0, IPC_RMID); return 0; }消费者端逻辑就是反过来的顺序先P(SEM_READY)读到数据之后V(SEM_EMPTY)。具体代码结构高度相似不重复展开。这段代码有两个容易出问题的地方初始化信号量必须用SETVAL显式确定初值否则semget创建出来的信号量初值是不确定的进程行为完全不可预测。ftok的路径要保证所有进程访问的是同一个文件否则key不一致创建者写的数据连接者根本找不到。3.7 共享内存的坑总结size不一致两侧shmget的size必须一致强烈建议统一用一个头文件定义宏。忘记同步没有同步控制的裸共享内存在高并发下必然出现覆盖和脏读这不是共享内存的bug是用法不对。残留对象程序崩溃后没有调用IPC_RMID内核对象留在系统里。调试时用ipcs -m检查、ipcrm -m清理。固定地址映射除了极少数特殊场景别给shmat传具体地址。4. 消息队列适合小块数据按类型收发4.1 消息队列解决的问题共享内存虽然快但要求所有通信方共享同一块内存同步得自己写。消息队列则在内核里维护一个带类型的消息链表发送方把消息投递进去接收方按需取出。它自带排队和阻塞机制也自带类型筛选很多场景下比裸共享内存省心。效率上消息队列比共享内存慢因为需要两次内核拷贝发送方从用户空间拷到内核链表接收方再从内核链表拷回用户空间。但好处很实在每条消息自带类型接收方可以只收某一类。内核负责排队天然支持多个发送者、多个接收者。不需要双方约定同一块连续内存小块数据传递非常方便。4.2 msgget、msgsnd、msgrcv 三个函数#include sys/msg.h int msgget(key_t key, int msgflg); int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);这里有一个极其常见的坑msgsnd和msgrcv的msgp参数不是直接指向纯数据而是指向一个结构体结构体第一个成员必须是long型的mtype。比如struct msgbuf { long mtype; char mtext[512]; };发送时msgsz参数是mtext的大小不是整个结构体的sizeof。接收时msgsz同样是缓冲区里mtext的最大大小。我见过好几个人第一次用给msgsnd传了sizeof(struct msgbuf)内核把mtype也当消息内容发出去接收端解析就乱了。msgtyp的规则msgtyp 0取队列中第一条消息。msgtyp 0取第一条类型等于msgtyp的消息。msgtyp 0取类型小于等于|msgtyp|的消息中类型值最小的那条。第三种规则比较偏日常开发用前两种就够。阻塞行为队列为空时msgrcv默认阻塞直到有消息到达。想非阻塞用IPC_NOWAIT标志但要注意区分返回-1时errno是ENOMSG队列空还是EINTR被信号打断。4.3 消息队列代码示例发送端/* msg_send.c */ #include stdio.h #include stdlib.h #include string.h #include sys/ipc.h #include sys/msg.h #include sys/types.h struct msgbuf { long mtype; char mtext[128]; }; int main() { key_t key ftok(/tmp/msg_demo, q); if (key (key_t)-1) { perror(ftok); exit(1); } int msqid msgget(key, IPC_CREAT | 0666); if (msqid -1) { perror(msgget); exit(1); } struct msgbuf msg; msg.mtype 1; snprintf(msg.mtext, sizeof(msg.mtext), this is a message from process); if (msgsnd(msqid, msg, sizeof(msg.mtext), 0) -1) { perror(msgsnd); exit(1); } printf(sent ok, msqid%d\n, msqid); return 0; }接收端/* msg_recv.c */ #include stdio.h #include stdlib.h #include sys/ipc.h #include sys/msg.h struct msgbuf { long mtype; char mtext[128]; }; int main() { key_t key ftok(/tmp/msg_demo, q); if (key (key_t)-1) { perror(ftok); exit(1); } int msqid msgget(key, IPC_CREAT | 0666); if (msqid -1) { perror(msgget); exit(1); } struct msgbuf msg; if (msgrcv(msqid, msg, sizeof(msg.mtext), 1, 0) -1) { perror(msgrcv); exit(1); } printf(recv: %s\n, msg.mtext); return 0; }如果队列里同时有mtype1和mtype2的消息指定msgtyp1就只会取出类型1。这在“日志消息一个类型、控制消息一个类型”的场景下非常方便。4.4 消息队列的坑消息大小限制Linux下单条消息有上限一般默认8192字节可以调内核参数。别往队列里塞超大结构体。队列总容量限制整个队列总大小默认在16KB量级超出后msgsnd要么阻塞要么返回EAGAIN用IPC_NOWAIT时。mtype不能为0mtype必须是正long值。传0msgsnd报EINVAL。msgsz别包含mtype发送和接收的msgsz都指mtext的大小这点太容易错值得反复强调。收错类型msgtyp0只保证取第一条不保证是你要的类型指定类型必须把msgtyp填对。5. 信号量进程同步的“交通信号灯”5.1 信号量不是数据是计数器我对初学者常说的一句话信号量不传数据它只是信号灯。它控制的是进程执行顺序、共享资源访问量。经典模型是一个计数器semval表示当前可用资源数量。P操作semop中的-1申请一个资源。如果semval大于0减1等于0进程阻塞等待。V操作semop中的1释放一个资源把semval加1并唤醒等待中的进程。这是荷兰计算机科学家Dijkstra提出的经典模型。PV操作这个名字从荷兰语“Proberen”测试和“Verhogen”增加而来。这个模型理解之后所有用信号量做互斥、同步的代码都是同一套思路的变体。5.2 单个信号量 vs 信号量集#include sys/sem.h int semget(key_t key, int nsems, int semflg); int semop(int semid, struct sembuf *sops, size_t nsops); int semctl(int semid, int semnum, int cmd, ...);semget的nsems表示创建“一组”信号量的数量。为什么需要一组因为共享内存生产者消费者场景里至少要用两个信号量一个管缓冲区空位一个管可消费数据。它们一起创建、一起删除逻辑上是一个整体。操作集合用struct sembuf描述struct sembuf { unsigned short sem_num; /* 信号量编号从0开始 */ short sem_op; /* 正数释放负数申请 */ short sem_flg; /* 0 或 IPC_NOWAIT */ };semop一次可以操作多个信号量。比如构造一个既有-1又有1的数组让多个信号量的变化在同一个原子操作里完成。这能避免“申请A成功但申请B失败”导致的死锁隐患。semctl常用命令命令作用SETVAL设置某个信号量的初值GETVAL查询当前值IPC_RMID删除信号量集注意SETVAL的参数类型是union semun。不同系统对这个union的定义位置不一致有的在头文件里有的需要自己定义。上文代码里我给出了通用定义。5.3 初始化信号量一个极易踩的坑semget创建的信号量初值是不确定的必须用semctl(SETVAL)显式初始化。很多新手包括当年的我直接在semget之后就调用semop结果有的进程瞬间冲到临界区有的直接卡死在P操作上行为完全随机。正确的流程是创建者负责初始化连接者只负责获取。判断创建者身份的方法就是IPC_CREAT | IPC_EXCLint semid semget(key, 2, IPC_CREAT | IPC_EXCL | 0666); if (semid -1 errno EEXIST) { /* 已存在说明我是连接者直接连接 */ semid semget(key, 2, 0666); if (semid -1) { perror(semget connect); exit(1); } } else if (semid -1) { perror(semget create); exit(1); } else { /* 我是创建者初始化信号量 */ }这种“先尝试独占创建失败再按已有对象连接”的双段式写法是System V IPC里遍布的通用模式。明白了它你就掌握了一种跨机制的判断技巧。5.4 信号量互斥代码示例下面是一个“多个进程互斥访问共享资源”的最简模型。两个进程同时对同一资源做累加不用信号量会出现竞态用信号量后结果就受控。/* worker.c —— 公共临界区操作 */ #include stdio.h #include stdlib.h #include sys/ipc.h #include sys/sem.h union semun { int val; struct semid_ds *buf; unsigned short *array; }; void P(int semid) { struct sembuf op {0, -1, 0}; if (semop(semid, op, 1) -1) { perror(semop P); exit(1); } } void V(int semid) { struct sembuf op {0, 1, 0}; if (semop(semid, op, 1) -1) { perror(semop V); exit(1); } } int main() { key_t key ftok(/tmp/sem_demo, s); if (key (key_t)-1) { perror(ftok); exit(1); } int semid semget(key, 1, 0666); if (semid -1) { perror(semget); exit(1); } for (int i 0; i 10000; i) { P(semid); /* 临界区这里可以做共享文件、共享内存上的互斥操作 */ V(semid); } printf(worker done\n); return 0; }对应创建者需要单独跑一段初始化代码/* sem_init.c */ #include stdio.h #include stdlib.h #include sys/ipc.h #include sys/sem.h union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { key_t key ftok(/tmp/sem_demo, s); if (key (key_t)-1) { perror(ftok); exit(1); } int semid semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); if (semid -1) { perror(semget create (maybe already exists)); exit(1); } union semun su; su.val 1; /* 初始值为1同一时刻只允许一个进程进入临界区 */ if (semctl(semid, 0, SETVAL, su) -1) { perror(semctl SETVAL); exit(1); } printf(semid%d, value%d\n, semid, semctl(semid, 0, GETVAL)); return 0; }实际项目里P和V之间的临界区尽量短别做耗时操作否则其他等锁的进程会一直阻塞。信号量就是锁的底层实现临界区不管长短本质都是“等待-进入-退出”。5.5 信号量的坑总结初始化遗漏不显式设初值行为不可预测。忘记删除进程退出不会自动清理信号量要用IPC_RMID。信号打断进程收到信号时semop可能返回EINTR代码里要做好重试处理。多信号量死锁两个进程各自持有一个信号量、又都在等对方释放形成循环等待就死锁了。常见对策是一次性申请多个信号量或者固定申请顺序。6. 三种机制的选型一张表和一个判断习惯三种System V IPC各有各的适用场景。我的建议非常简单数据量大、追求极限吞吐共享内存。前提是你愿意写同步控制逻辑并且能接受它不提供排队。消息种类多、数据量小、需要按类型筛选消息队列。只是协调执行顺序或控制访问量信号量。机制主要用途速度是否需要额外同步数据结构共享内存大数据量共享、高性能通信最快几乎无拷贝必须自己控制裸内存自定义任意结构消息队列小数据量、按类型收发中等两次内核拷贝自带队列不需要锁消息带类型可排队信号量进程间互斥、同步、资源计数极快但不传数据本身就是同步手段计数器数组这三种机制还经常组合使用最典型的就是“共享内存信号量”。共享内存负责大流量数据搬运信号量负责读写顺序控制分工明确。这个组合也是我目前做多进程高性能通信时的首选方案。7. 实战复盘模拟项目X里如何用System V IPC解决多进程分发问题回到开头的模拟项目X。最终架构是三个进程共享一块环形缓冲区外加两组信号量。进程A负责写从数据源读取数据写入共享内存写之前P(空位)写之后V(数据就绪)。进程B负责处理P(数据就绪)从共享内存读数据并聚合计算把结果写到另一块共享区域处理完V(处理完)。进程C负责落盘P(处理完)从结果区域读取并写文件最后V(空位)。本质上是一组信号量在充当全局状态机。刚开始运行时发现进程A写太快、进程B处理不过来空位信号量长期为0A一直阻塞。后来给“空位”信号量设了阈值而不是简单的0/1让缓冲区囤积数据流水线吞吐量立刻上来了。这个思路也值得参考不要把信号量永远当成0/1的二元锁它是一个可调的计数器资源数量本身是一个可以设计的参数。另一个重要的点是共享内存结构体的内存布局。多进程必须共用同一个头文件定义的数据结构任何进程都不能单独加字段。不同进程如果使用了不同的结构体定义编译时的对齐方式就可能不同读出来就是错位数据。我们把结构体定义锁死在一个common.h里代码评审时专门检查这条规则。最后一个教训是清理。进程异常退出时IPC对象不会自动消失残留对象积累多了会导致key冲突。后来我们加了一个守护模块进程结束前统一调用shmctl(IPC_RMID)和semctl(IPC_RMID)清理自己创建的对象。程序健壮性明显提升了一个档次。8. 从System V IPC到POSIX IPC什么时候该换如果从今天的视角回看System V IPC的问题在于接口偏旧、API复杂、对象生命周期不稳定、进程退出时容易残留。Linux也提供了一套更现代的POSIX IPC接口shm_open、mq_open、sem_open等。有些场景下POSIX IPC确实用起来更顺手尤其是共享内存部分按文件描述符方式操作比较自然。我的建议是维护已有项目时项目已经基于System V IPC就继续用不要动辄大规模重写。写全新项目时如果团队对POSIX IPC更熟可以考虑POSIX IPC。做底层、跨平台可移植性要求高的模块时POSIX IPC的可移植性通常更好。但说到底System V IPC是理解Linux进程通信的一把钥匙。它不复杂就是内核为你提供的三种“公共空间”进程之间凭key找到它们再用一套get/op/ctl函数操作。在模拟项目X之后我又陆续做过很多进程间协作的项目每当遇到“要共享数据、要同步协调、要按消息收发”的问题第一反应就是在这三种机制里选型而不是先考虑Redis、网络socket这些更重的外部依赖。能用一个内核原语解决的问题就不引入新服务。这也是我写这篇文章的初衷System V IPC应该成为每个Linux从业者工具箱里的常备工具而不是等到数据延迟爆炸时才想起去翻书。如果你接下来要继续深入“多进程并发性能优化”方向后面值得研究的是内存屏障、无锁队列、CPU亲和性、hugetlb大页共享内存。这些话题都跟今天聊的基础密不可分。先把System V IPC搞踏实了再往上堆性能优化手段就顺多了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询