Harshit Batra
← Back to Resources

Operating System

Pipes, Sockets, Message Queues: Picking the Right IPC Isn't About Speed

Ranking IPC mechanisms by speed (shared memory fastest, sockets slowest) isn't wrong, but it's not how you'd actually pick one. The real question: what relationship do these two processes already have to each other?

Why processes need IPC at all

Processes don't share memory by default — that's the point of process isolation. Even a parent and child right after fork() have identical but separate memory images. The moment application logic splits across multiple processes — different languages, different teams, different scaling needs — those processes need an OS-provided channel to exchange anything. That channel is IPC.

A typical web app: a C/C++ web server handles raw HTTP, hands dynamic requests to a separate application server (often a scripting language, for easy text handling), which talks to a database process. Three processes, three languages, one application — none of it works without IPC gluing it together.

The mechanisms, by relationship

Relationship Mechanism Why
Parent-child, same fork() Pipe Unidirectional stream (pipe() returns a read end and write end) — this is exactly what ls | grep foo uses
Unrelated processes, same machine, need coordination Message queue Mailbox abstraction — one process posts, another retrieves later, OS buffers in between
Client-server, possibly cross-machine Socket IP address + port, works identically on one machine (Unix domain sockets) or across a network
Same machine, need raw throughput, will handle sync yourself Shared memory Two processes map the same physical memory via shmget() — fastest, because there's no OS-mediated copying, but the OS gives you zero synchronization; uncoordinated writes corrupt data
"Something happened," no data payload Signal SIGINT, SIGKILL, SIGCHLD — single-bit notifications, not data. Ctrl+C is the OS delivering one specific signal, not sending a message

The detail that actually causes production bugs: blocking

Reading from an empty pipe/socket/queue blocks by default; writing to a full one blocks too. Every mechanism has non-blocking variants. Whether a given piece of code uses the blocking or non-blocking version is usually the actual root cause when a server mysteriously "hangs" under load — if you're debugging a stuck process and IPC is involved, "is this call blocking, and on what" is the first question to ask.

Quick recall

  • Pipes: parent-child via one fork(). Message queues: unrelated processes needing coordination. Sockets: client-server, possibly cross-machine. Shared memory: fastest, but you own synchronization yourself. Signals: single-bit notifications, not data.
  • Picking shared memory for speed, then hand-rolling a locking scheme, is worse than picking a message queue that was already correct for the relationship
  • Reading an empty channel or writing to a full one blocks by default — check blocking vs. non-blocking first when something "hangs"

Quick quiz

  • 1. Which IPC mechanism gives zero OS-provided synchronization — fastest, but you handle locking yourself?

  • 2. A parent and its child right after fork() want to communicate. What's the natural mechanism?

  • 3. What's usually the real root cause when a server mysteriously 'hangs' and IPC is involved?