☰
Linux进程间通信IPC全解析:管道、共享内存、消息队列与信号量等
2026/10/29 3:58:24 网站建设 项目流程

记得我刚工作那会儿,第一次在一个后台服务里遇到两个子进程需要互相传数据,我第一个念头是写文件。可是文件要加锁、要清理、要考虑并发读写,麻烦不说,性能也扛不住。后来带我的老工程师丢给我一句话:去把Linux进程间通信那点东西搞明白。这一搞,就是好多年。

Linux下进程间通信(IPC)这块内容,可以说是所有后台开发、嵌入式开发、运维同学躲不开的坎。它不像数据库、网络协议那么“显眼”,但系统里几乎每一个稍微像样的服务,都在用IPC在底层串联。面试也爱考,工作中更是天天见——你今天用管道把nginx日志交给分析脚本,明天可能就在代码里用共享内存交换实时数据,后天排查线上问题,发现是消息队列堆积导致服务卡死。

这篇合集我尽量把Linux下主流的进程间通信方式一次性讲透,包括管道、FIFO、消息队列、共享内存、信号量、信号、本地套接字,以及它们背后的设计思路、适用场景、常见坑。内容有点长,但每一条都是可以直接拿到生产环境用的经验,也适合面试前通读一遍。

1. 为什么Linux下进程通信方案这么多

1.1 IPC到底解决什么问题

先捋一个最简单的问题:进程是独立的内存空间,一个进程里的全局变量,在另一个进程里根本看不见。所以要让两个进程协作,就必须有一种机制,把数据从一个地址空间搬到另一个地址空间,或者至少能让双方感知到对方的状态变化。这个机制就是进程间通信,英文叫IPC,Inter-Process Communication。

你可能会问,为什么不用多线程?多线程共享同一份内存,通信不是更容易吗?问题在于,多线程的崩溃是“连坐制”的——一个线程段错误,整个进程死给你看。而多进程天然隔离,一个进程挂了,另一个还能继续干活,这也是很多高可用服务坚持用多进程模型的原因。但隔离的代价,就是通信变得麻烦,所以IPC这套东西应运而生。

理解IPC的重点,不只是知道每个API怎么调用,而是要搞明白每一类IPC解决的是哪类痛点:是要传输数据,还是要通知事件?是追求吞吐量,还是要求可靠性?是单机内通信,还是网络通信?不同答案对应不同方案,选错了后面全是眼泪。

1.2 IPC全景图与选型对照

我把Linux下主流的IPC方案按“传输能力”和“使用场景”做了个梳理,先看整体面貌:

IPC方式数据能力适用场景典型特征
匿名管道字节流父子进程间单向传递随进程创建,用完即走
命名管道FIFO字节流无亲缘关系进程间通信需要文件系统中的路径名
System V消息队列结构化消息多进程按消息通信有类型、有优先级
System V共享内存大块内存数据高性能大数据量交换最快,但需配合同步
System V信号量计数值多个进程或线程互斥、同步本质是计数器,不是传数据
信号signal事件通知异步事件处理、进程控制不携带大数据,异步
本地套接字Unix Domain Socket字节流/数据报本机任意进程通信,最灵活可用select/epoll,稳
mmap文件映射大块数据+持久化大数据共享、文件IO加速共享内存的另一种实现
文件锁/flock锁状态多进程互斥访问资源防并发,不传数据

先记住这张表,后面每一类我都会展开讲。里面最重要的是共享内存、消息队列和本地套接字,基本上生产环境80%的IPC需求都落在它们身上。

1.3 选型时先想清楚的三件事

第一件事:数据量有多大。如果是少量控制指令,用管道、消息队列、socket都很舒服;如果是几百MB甚至几GB的帧数据,老老实实上共享内存,别的方案性能都撑不住。

第二件事:实时性要求多高。消息队列天然有排队延迟,管道也可能因为缓冲区满而阻塞,socket有内核缓冲。共享内存只要写进去,对方立刻能看到,实时性最好。

第三件事:这次通信是一次性的,还是长期持续的。一次性传文件用管道或socket配合发个文件名就完事;长期高频协作的进程组,就应该规划好固定的共享内存段和信号量,把基础设施一次性建好。

2. 管道:最基础也最容易被忽略的IPC

2.1 匿名管道:父子进程之间的“临时天桥”

管道是最早出现的IPC方式之一,也是Unix哲学“小工具组合成大系统”的基础。你在shell里写的cat file | grep keyword,本质上就是创建了两个进程,并用管道把第一个进程的标准输出接到第二个进程的标准输入。

C语言里创建匿名管道只需一个函数:

#include <unistd.h> #include <stdio.h> #include <string.h> int main() { int pipefd[2]; if (pipe(pipefd) == -1) { perror("pipe"); return 1; } pid_t pid = fork(); if (pid == 0) { // 子进程:关闭写端,读数据 close(pipefd[1]); char buf[128] = {0}; read(pipefd[0], buf, sizeof(buf)); printf("子进程收到: %s\n", buf); close(pipefd[0]); } else { // 父进程:关闭读端,写数据 close(pipefd[0]); const char *msg = "hello from parent"; write(pipefd[1], msg, strlen(msg)); close(pipefd[1]); wait(NULL); } return 0; }

注意几个细节。第一,管道是半双工的,数据只能从写端流向读端,如果你想双向通信,得建两个管道。第二,fork之前先创建管道,fork之后父子进程都继承了管道fd,所以必须一方关闭读端、一方关闭写端,否则另一半没用到的fd不关,会导致对方读不到EOF,卡死在read上。第三,read默认是阻塞的,如果写端一直不关,读端会一直等下去。

管道这个机制虽然朴素,但底层有内核缓冲区,默认大小一般是64KB(可以查/proc/sys/fs/pipe-max-size,用fcntl(fd, F_SETPIPE_SZ, size)调整)。小数据吞吐完全够用,但写入超过缓冲容量会阻塞,这是很多第一次写管道代码的人踩过的坑。

2.2 命名管道(FIFO):让无关进程也能对话

匿名管道有个硬伤:只能在有亲缘关系的进程间用,因为没有路径名,别的进程根本找不到它的入口。命名管道(FIFO)就是给管道加了个文件系统里的“门牌号”。

创建方式很简单:

#include <sys/types.h> #include <sys/stat.h> // 在命令行创建 // mkfifo /tmp/myfifo

或者在代码里:

mkfifo("/tmp/myfifo", 0644);

一个进程用open("/tmp/myfifo", O_WRONLY),另一个进程用open("/tmp/myfifo", O_RDONLY),两边就能像读写文件一样通信了。这里有个经典行为:以只读方式打开FIFO会阻塞,直到有写方打开;反过来也一样。如果你不想阻塞,open时加O_NONBLOCK标志。

我实际用过一次FIFO,是在一个采集程序里,数据采集进程和上传进程完全解耦。采集进程只管把数据streaming地写进FIFO,上传进程自己决定什么时候读、读多少。好处是不用改采集进程的代码,随时可以停掉上传进程做维护,数据在内核缓冲里缓存着,起来了接着读。当然,FIFO的数据不具备持久性,读走就没了,进程全退出后数据也就丢了。

2.3 管道的坑与注意事项

  • 管道是流式的,没有消息边界。进程A发送hello和world,进程B可能一次性读到helloworld,也可能分开读两次。所以要么自己定义分隔符,要么采用固定长度消息结构。
  • 写端在读端全部关闭后继续写,会收到SIGPIPE信号,进程默认会被直接终止。这个坑很隐蔽,我在一个网络代理程序里遇到过,排查半天愣是没想到是管道的问题。处理方式是要么忽略SIGPIPE,要么在write后检查errno == EPIPE并做优雅退出。
  • 单条数据超过PIPE_BUF(通常4096字节)时,write不保证原子性,多进程同时写可能把数据交错在一起。这个特性在O_NONBLOCK场景下尤其容易踩。

3. System V IPC三剑客:消息队列、共享内存与信号量

3.1 消息队列:让消息有类型、有秩序

管道传递的是无序字节流,但很多场景下我们传的是结构化的“消息”,比如请求类型、请求ID、数据体。System V消息队列就是干这个的,每条消息有mtype字段,接收方可以按类型提取消息,天然支持按优先级读取。

发送消息的API是msgsnd,接收是msgrcv。我整理了一个最简单的发送端示例:

#include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <string.h> struct msgbuf { long mtype; // 消息类型,必须 > 0 char mtext[256]; // 消息数据 }; int main() { int msqid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); if (msqid == -1) { perror("msgget"); return 1; } struct msgbuf msg; msg.mtype = 1; strcpy(msg.mtext, "hello message queue"); if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) { perror("msgsnd"); return 1; } printf("消息已发送, msqid=%d\n", msqid); // 生产环境别忘在适当时机用 msgctl(msqid, IPC_RMID, NULL) 删除 return 0; }

接收端用msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0),其中第四个参数是mtype,指定接收哪类消息。

消息队列的优势在于:第一,支持多进程同时读写,内核会处理同步,不像管道需要自己小心“并发写交织”;第二,消息有类型,可以按业务维度分发;第三,生命周期是内核级的,只要不显式删除,进程退出后队列还在。

不过它也有不少问题:消息大小有限制,默认单条上限MSGSZ一般是8KB;不支持随机访问,只能按顺序消费;System V消息队列接口老旧,POSIX消息队列也没好多少,所以新代码里我其实很少用它。但它仍然是经典面试题,你手上有没有处理过消息堆积的排障经验,是面试官判断你实战水平的一个点。

3.2 共享内存:性能王者的正确打开方式

如果要给IPC排一个性能榜,共享内存毫无悬念是第一。它的原理粗暴而高效:把同一段物理内存映射到多个进程的虚拟地址空间里,大家直接读写同一块内存,压根不需要“拷贝”数据——内核不参与数据传输,只负责建立映射关系。

System V共享内存三步走:

#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> int main() { // 1. 创建/获取共享内存,1KB int shmid = shmget(IPC_PRIVATE, 1024, 0666 | IPC_CREAT); if (shmid == -1) { perror("shmget"); return 1; } // 2. 把共享内存挂到本进程地址空间 char *addr = shmat(shmid, NULL, 0); if (addr == (void *)-1) { perror("shmat"); return 1; } // 3. 直接读写 strcpy(addr, "shared data"); printf("共享内存内容: %s\n", addr); // 用完分离,生产环境还要在合适时机删除共享内存 shmdt(addr); return 0; }

另一个进程拿到同一个shmid,也能shmat挂上来,看到这块内存里写入的内容。极简、极快,一个memcpy级别就能完成“通信”。

但共享内存有个非常致命的问题:它不提供任何同步机制。两个进程同时写,数据就会乱掉。所以共享内存必须配合信号量(或互斥锁、读写锁)使用。这也是为什么我在很多项目里见到的“共享内存方案”都写着“共享内存 + 信号量”这个固定组合。

另外,System V共享内存不会随进程退出而自动清理,一直是内核里的一块“钉子户”。如果创建了大量共享内存段又不及时IPC_RMID,系统资源很快就被吃光。用ipcs -m查看,用ipcrm -m shmid清理,是运维的基本操作。

3.3 信号量:为共享资源加一把“秩序锁”

信号量常被误认为是一种通信方式,其实它是“同步工具”,不是用来传输数据的,而是保护共享资源不被多个进程同时破坏的。

信号量的本质是一个非负整数,支持两个原子操作:P操作(semop里的SEM_UNDO相关)把计数器减一,如果减到负数则阻塞;V操作把计数器加一,唤醒正在等待的进程。生产上常见的是二元信号量,相当于一个互斥锁,取值只有0和1。

System V信号量的API是semget、semop、semctl,但说实话,这套API设计得挺反人类的——操作集合用struct sembuf数组表达,初始化还得走semctl的SETVAL,每次写都要翻手册。我给你一个极简的PV封装:

#include <sys/sem.h> // 对编号为 semnum 的信号量做 P 操作(减1,资源占用) static void sem_p(int semid, int semnum) { struct sembuf op = {semnum, -1, 0}; semop(semid, &op, 1); } // 做 V 操作(加1,资源释放) static void sem_v(int semid, int semnum) { struct sembuf op = {semnum, 1, 0}; semop(semid, &op, 1); }

这里的第三个参数flg,我强烈建议你加SEM_UNDO。它的作用是,如果进程在持锁期间崩溃了,内核算账时会自动把信号量恢复,防止死锁。不加这个标志,一旦进程挂掉,资源就永远锁死,其他进程全部卡住。这个坑我在线上见过不止一次,加了SEM_UNDO能省掉很多半夜被叫醒的痛苦。

3.4 System V IPC的共性:钥匙与生命周期

System V家族(消息队列、共享内存、信号量)有几个共同点。第一,它们都通过key(一般是ftok函数生成)来标识,之后系统返回一个id,进程间只要key一样,就能找到同一个IPC对象。第二,它们都是内核级对象,进程退出不影响它们的存在,必须显式删除。第三,它们都有权限位,类似文件权限的0666之类的设置。

单进程退出不清理,听上去是个特性,但没有安全意识的话就成了坑。我以前管理过一台长期运行的服务器,有个程序每次启动都创建共享内存但从不删除,跑了半年后ipcs -m一看,几十个段堆在那里。虽然单个不大,但加起来也不小,而且排查问题时一眼望去全是历史垃圾,分不清哪块是活数据。所以代码里创建IPC对象后,一定要在合适的时机调用清理接口,或者至少用ipcrm定期打扫。

4. POSIX IPC与mmap:更现代的替代方案

4.1 POSIX消息队列和System V的核心差异

System V IPC虽然经典,但接口古老,有些设计已经不适合现代工程。POSIX标准在20世纪90年代推出了一套新的IPC接口,形态上更贴近文件操作,用名字而不是key来标识对象,使用起来直观很多。

POSIX消息队列的关键函数是mq_open、mq_send、mq_receive,对象名是/mqname这样的字符串。它相比System V消息队列有几点优势:

  • 支持通知机制(mq_notify),可以注册回调,消息到达时通过信号或线程通知接收者;
  • 超时控制更完善,mq_timedreceive可以精确指定超时时间;
  • 轮询友好,可以把队列fd放进select、poll、epoll等IO多路复用机制里。

不过POSIX消息队列也有个众所周知的限制:默认队列大小、单条消息大小上限,受/proc/sys/fs/mqueue/msg_max等内核参数限制,而且某些老版本内核上行为不一致。所以如果你只是做单机进程间的异步消息,我依然觉得权衡之后,本地套接字往往比任何消息队列实现都更省心(这个后面会讲)。

4.2 mmap:把文件搬进虚拟内存的魔法

mmap是另一种创建共享内存的方式,和System V共享内存的核心区别在于:它的映射对象可以是文件,也可以是匿名内存。文件映射的好处是,进程崩溃后数据不掉,重启后还能从文件里恢复;匿名映射则纯粹是内存通信。

mmap最经典的生产用途有两个。

一个是高性能本地缓存——把一个几十GB的索引文件映射进虚拟内存,读写像操作内存一样,由内核的page cache来管理真正落盘。搜索引擎里的倒排索引就常这么干。

另一个是跨进程大数据共享——两个进程通过mmap映射同一个文件,直接读写这块内存就能传递数据。因为底层是文件,天然具备持久化能力,不需要像System V共享内存那样自己额外做持久化。

创建一个简单匿名映射的原型:

#include <sys/mman.h> #include <stdio.h> #include <string.h> #include <unistd.h> int main() { size_t len = 4096; // MAP_SHARED 表示多进程共享,MAP_ANONYMOUS 是匿名映射 char *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) { perror("mmap"); return 1; } strcpy(addr, "hello mmap"); printf("映射内容: %s\n", addr); munmap(addr, len); return 0; }

注意,MAP_SHARED是所有共享的根基,如果误写成MAP_PRIVATE,则每个进程都会有自己的副本,写操作不会互相同步,共享也就名存实亡了。

使用mmap有几个要小心的地方:

  • 映射大小对齐:mmap映射的起始地址和长度都要按页对齐(sysconf(_SC_PAGESIZE),一般是4096)。文件映射时,如果文件长度不是页的整数倍,最后一个不完全页的剩余部分会是零填充,不能写。
  • 读写越界:映射区域不像malloc内存那样有检查,越界写可能直接写进文件或弄崩别的映射区,排查起来十分痛苦。
  • 同步:mmap本身也不带锁,跨进程互斥需要配合信号量或文件锁。多进程同时写同一个文件的同一块映射区,数据照样会破坏。

4.3 文件锁与线程同步的补充

在“同步”这件事上,还有一个容易被忽略的方案——文件锁flock或者fcntl记录锁。它们不传数据,但能保证多进程对同一资源(比如配置文件、共享文件、共享内存)的访问是互斥的。

我曾经在一个场景里用flock实现了一个多进程分布式下的“单实例保证”:多个服务进程启动时,都尝试对同一个锁文件加排他锁,谁成功谁当主进程,其他进程自动退出。不需要引入外部协调组件,轻量且可靠。

另一个补充是:多线程之间的互斥和同步,不建议使用System V信号量,直接用pthread的mutex、条件变量就够了。它们更轻、更快,而且随进程生命周期自动清理,不会在内核里留下垃圾对象。

5. 信号与Socket:被低估的两种IPC方式

5.1 信号:适合通知,不适合传数据

信号(signal)在严格意义上也属于进程间通信,但它的定位是“异步事件通知”。比如SIGINT(Ctrl+C)、SIGTERM(终止)、SIGCHLD(子进程退出)、SIGUSR1/SIGUSR2(用户自定义),都是很典型的使用场景。

信号的好处是:效率高,异步,系统帮你处理了几乎所有底层调度;你不用建立什么队列、共享内存,一条kill命令就能让另一个进程跑某个处理器函数。

但它有明显短板:信号不携带数据,只能表示“某个事件发生了”,至于事件的具体内容,进程只能去别的地方拿;信号的发送和处理是异步的,如果在信号处理器函数里调用非异步信号安全函数(比如printf、malloc),可能导致死锁或未定义行为。

一个安全实践是:信号处理器函数里只做最简单的事——比如设置一个全局标志位,然后把真正的业务处理丢给主循环。伪代码如下:

#include <signal.h> #include <stdio.h> #include <unistd.h> static volatile sig_atomic_t g_flag = 0; void handler(int sig) { g_flag = 1; // 只做标志位赋值,安全 } int main() { signal(SIGUSR1, handler); while (1) { if (g_flag) { printf("收到 SIGUSR1,开始处理业务\n"); g_flag = 0; } sleep(1); } return 0; }

volatile sig_atomic_t这个类型值得记住——它能保证读写在信号处理器和主循环之间的原子性,比普通int靠谱得多。

5.2 本地域套接字:最灵活的生产级IPC

如果让我从现代工程角度选一个“万能IPC”,我会选Unix Domain Socket(本地域套接字)。它的调用方式和网络socket几乎一样,但它不需要走网络协议栈,不需要IP和端口,而是通过文件系统路径(比如/tmp/myserver.sock)来标识。所以性能非常高,同时又能复用你已有的socket编程经验。

为啥说它“万能”:

  • 支持流式(SOCK_STREAM),保证有序、无丢失,适合可靠传输;
  • 支持数据报(SOCK_DGRAM),报文有边界,适合请求响应模型;
  • 天然可以接入select、poll、epoll多路复用,适合高并发服务;
  • 允许传递文件描述符(通过sendmsg的辅助数据),这个功能很硬核,可以做到把一个已经打开的文件、socket“转交”给另一个进程,很多高性能服务器做平滑重启就是靠它;
  • 权限控制非常自然——用文件权限位即可,比System V的权限位更直观。

我举一个极简的服务端和客户端骨架。服务端创建本地套接字并监听:

#include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <unistd.h> int main() { int lfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/tmp/test.sock"); unlink("/tmp/test.sock"); // 清理历史残留 bind(lfd, (struct sockaddr *)&addr, sizeof(addr)); listen(lfd, 8); int cfd = accept(lfd, NULL, NULL); char buf[128] = {0}; read(cfd, buf, sizeof(buf)); printf("收到: %s\n", buf); close(cfd); close(lfd); unlink("/tmp/test.sock"); return 0; }

客户端就简单多了:socket(AF_UNIX, SOCK_STREAM, 0),然后connect到那个路径,再write数据。

使用本地套接字要注意一个问题:socket文件是残留物,进程退出后不会自动删除。如果程序反复启动,下次 bind 会失败。所以服务端启动时最好unlink一次,就像上面代码演示的那样。

我个人的线上经验是:很多现代组件,比如PostgreSQL、Redis、Nginx、Systemd,都在用Unix Domain Socket做本地管理通道和主从通信。它的通用性、稳定性、可调试性远超过老式IPC。如果你只想学一种IPC,我第一个推荐学它。

6. 常见问题排查与实用技巧

这块直接上干货,都是我在实际调试里反复用到的经验。

6.1 经典错误码与排查方向

错误码出现场景排查方向
EACCES打开IPC对象或socket时权限不足检查0666权限、socket文件目录权限
EAGAIN非阻塞模式下读写管道/消息队列/socket,暂时无数据或缓冲区满重试或等待IO事件
EINTR读写过程中被信号打断判断errno后重试当前操作
EPIPE管道读端关闭后继续写该处理SIGPIPE信号,或检查对端是否存活
ENOENT使用不存在的消息队列/共享内存/socket文件确认创建流程是否执行,检查key或路径
ENOMEM共享内存或mmap内存不足free、ipcs -m、cat /proc/meminfo
EIDRMIPC对象被删除后进程还在等待检查是否有别的进程或运维命令误删了对象

6.2 用好内核命令,事半功倍

  • ipcs:查看当前系统的消息队列、共享内存、信号量。ipcs -m、ipcs -q、ipcs -s分别查看三类对象。我排查内存泄漏时几乎必用。
  • ipcrm:删除指定的IPC对象。ipcrm -m shmid删共享内存,ipcrm -q msqid删消息队列,ipcrm -s semid删信号量。
  • lsof /tmp/test.sock:看哪个进程正占用某个socket文件。
  • strace:这是调试IPC的神器。strace -f -e trace=ipc,network ./your_program可以跟踪到所有IPC系统调用,包括shmget、semop、sendto等细节,瞬间定位到是哪一步调用失败。

6.3 我踩过的那些坑

多进程同时写管道导致数据交织——这是最早期的坑,后来我统一改用消息队列或本地socket,让每条消息有边界,问题才消失。

共享内存被进程意外改坏——因为多个模块都往同一块共享内存里写,没有统一规划数据布局和读写协议,结果数据串了。后来我强制约定:所有共享内存都必须配一个结构头,包含魔数、版本号、数据长度、校验值,写入前后都校验,任何一方发现版本对不上就拒绝读写。这个习惯救了我很多次。

忘记处理SIGPIPE导致进程莫名退出——在管道和socket的write端,如果对端已经关闭连接,默认行为是进程收到SIGPIPE被终止。尤其在网络服务里,一个请求还没发完对端就断开,你整个服务进程都可能挂掉。处理方式是在启动代码里signal(SIGPIPE, SIG_IGN),然后在write返回值里检查EPIPE。这是每个写网络后端的人都应该知道的基本操作。

ftok生成的key冲突——ftok依赖文件路径和一个整数,文件路径相同、整数相同,生成的key就相同。但“相同key”不代表“同一个IPC对象”,两个不相关的程序如果用了同一条路径做ftok,可能意外拿到同一个消息队列。排错时百思不得其解,后来发现是两个服务复用了同一个配置文件路径。解决办法是给不同业务用不同的路径,或者直接用IPC_PRIVATE+ 某种方式传递id(比如写入文件、通过socket传)。

7. 面试与实战中的高频问题

这个合集既然叫“超级合集”,我也把面试里关于IPC的高频考点一并梳理一下,给准备跳槽的同学一个速查方向。

  • 什么是僵尸进程,和IPC有什么关系?子进程退出后如果父进程没回收,会留下僵尸进程。处理方式是用SIGCHLD配合wait回收子进程。
  • 管道和消息队列的本质区别是什么?管道是字节流、无边界;消息队列是记录型的、有类型。
  • 共享内存为什么是最高效的IPC?因为它没有数据拷贝,直接读写内存;而管道和消息队列的send/recv都涉及用户态和内核态之间的拷贝。
  • 多进程使用共享内存时怎么保证同步?用信号量、互斥锁、原子操作,或者设计成单写多读模型。
  • 信号量是锁吗?不完全是。锁是“互斥”,信号量可以做到“计数协调”,比如控制同时有N个进程进入临界区。
  • 本地套接字和网络socket有什么不同?本地socket不需要协议栈、不需要网卡,效率更高,适合本机通信用;网络socket可以跨机器。
  • 什么场景下不适合用共享内存?数据需要持久化、需要跨网络传输、需要动态按需分配、进程模型变化频繁时,都不适合。共享内存是静态分配的,灵活性差。

吃透这些,不仅面试能打,实战选型时也会更有底气。

最后再分享一个小技巧:如果你新到一个项目组,想快速了解这个系统的IPC设计,别急着翻代码,先跑一遍ipcs和lsof,看看系统里有哪些IPC对象和socket文件,谁能创建谁在用,基本就能摸清这套系统的骨架了。我在排查线上问题时经常这么做,往往比看代码还快。希望这篇合集对你也有同样的价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询