老是记不住,进程间通信与线程通信
- 进程间通信 (IPC) 核心挑战:因为进程拥有独立的虚拟地址空间,一个进程无法直接访问另一个进程的数据。操作系统必须提供专门的机制来在它们之间搭建桥梁。
主要方式:
管道 (Pipe)
无名管道:用于具有亲缘关系(父子、兄弟)的进程间通信。它是一种单向的、半双工的字节流。shell 中的 | 命令就是使用的管道。
命名管道 (FIFO):通过一个文件系统中的路径名来标识,允许无亲缘关系的进程进行通信。
消息队列 (Message Queue)
消息的链表,存放在内核中。进程可以向队列中添加消息或从中读取消息。
优点:克服了管道只能承载无格式字节流的缺点,可以指定消息类型。
缺点:数据需要在内核和用户空间之间拷贝,有一定性能开销。
共享内存 (Shared Memory)
最快的一种IPC方式。多个进程将同一块物理内存映射到它们各自的地址空间。数据不需要在内核中拷贝,进程可以直接读写这块内存。
核心问题:需要配合进程间同步机制(如信号量、互斥锁)来避免竞态条件,因为对共享内存的访问是异步的。
信号量 (Semaphore)
本质上是一个计数器,用于同步,而不是直接传递数据。它控制多个进程对共享资源的访问,常用于配合共享内存使用。
信号 (Signal)
一种异步通信机制,用于通知接收进程某个事件已经发生(如 Ctrl+C 发送 SIGINT 信号)。可以看作是进程版的“中断”。
套接字 (Socket)
最通用的IPC方式,不仅可以用于同一台主机的进程间通信,更能用于网络中不同主机上的进程间通信。
- 线程间通信 核心特点:由于同一进程下的多个线程共享地址空间(全局变量、堆空间等),因此线程间通信天然地非常高效和简单,其核心挑战不在于“通信”,而在于如何安全、同步地访问这些共享资源,即线程同步。
主要方式:
全局变量/共享内存
线程可以直接读写进程的全局变量或堆内存。这是最直接的方式。
必须同步:不加保护地读写共享数据会导致竞态条件(Race Condition)和数据不一致。
互斥锁 (Mutex)
用于保护临界区(一段访问共享资源的代码),保证同一时间只有一个线程可以进入。这是最基础的同步原语。
条件变量 (Condition Variable)
用于线程间的等待和通知。当一个线程需要等待某个条件成立时,它会休眠在条件变量上;当另一个线程改变了条件时,它可以通知(唤醒)等待的线程。
通常与互斥锁结合使用。
信号量 (Semaphore)
在线程间同样适用,可以看作是一个更通用的计数器,用于控制访问共享资源的线程数量。
读写锁 (Read-Write Lock)
允许多个读线程同时访问,但写线程必须独占访问。适用于“读多写少”的场景,能提高并发性能。
屏障 (Barrier)
让一组线程在某个点同步等待,直到所有线程都到达这个点,才继续执行。
核心区别总结与对比表格特性 进程间通信 (IPC) 线程间通信通信本质 数据传输(跨越独立的地址空间) 状态同步/数据同步(在共享的地址空间内)通信成本 高。通常需要系统调用,数据可能需要在用户态和内核态之间拷贝。 极低。直接读写内存,速度快。核心挑战 如何建立通信渠道并交换数据 如何安全地同步对共享资源的访问,避免竞态条件主要机制 管道、消息队列、共享内存、信号、套接字 互斥锁、条件变量、信号量、屏障操作系统介入 需要。几乎所有IPC机制都由内核提供和管理。 部分需要。同步原语(如互斥锁)需要内核支持以实现真正的阻塞;但简单的内存访问不需要。可靠性影响 高。一个进程崩溃通常不会影响其他进程。 低。一个线程崩溃会导致整个进程及其所有线程崩溃。如何选择?需要高隔离性、高可靠性? -> 选择多进程 + IPC(如Chrome浏览器,每个标签页是一个独立进程)。
需要高性能、高并发、大量数据共享? -> 选择多线程 + 同步机制(如Web服务器,处理大量并发请求)。
既要隔离性又要共享数据? -> 共享内存 + 信号量 是经典的折中方案。
总而言之,进程间通信像是两个不同国家的人要通过翻译和邮政系统来交换货物;而线程间通信像是同一个办公室的同事,可以直接说话,但需要会议纪律(同步)来避免大家七嘴八舌同时说。 理解这个比喻,就能深刻理解它们的区别。