Module 03 · Processes and threads · Lesson 5 of 7
Inter-process communication
How isolated processes exchange data: pipes, Unix domain sockets, shared memory, message queues and files. What each costs in copies and system calls, which give ordering or structure, and how the choice maps to sidecars and local services.
Why it matters
Processes are isolated on purpose, but real systems need them to cooperate: a shell pipeline, a web server talking to a local PHP or Python worker, an application and its sidecar proxy, a database and its replication helper, a browser's renderer and its main process. The mechanism chosen decides latency, throughput and how much code you write. A service mesh sidecar adds a hop through exactly these mechanisms to every request, which is why their cost matters.
The idea
Because processes cannot read each other's memory, every inter-process communication (IPC) mechanism goes through something the kernel provides: a kernel buffer that data is copied into and out of, a region of memory the kernel maps into both processes, or a file.
| Mechanism | Shape | Copies per message | Fits |
|---|---|---|---|
| Pipe | One-way byte stream between related processes (anonymous) or by name (FIFO) | Two: into the kernel, out again | Shell pipelines, a parent feeding a child |
| Unix domain socket | Bidirectional stream or datagram, addressed by a file path; can pass file descriptors | Two, but no network stack | Local services: database clients, Docker's API, app to sidecar |
| TCP over loopback | Same API as remote networking | Two, plus the TCP/IP stack | Code that might later run on another machine |
| Shared memory | A region mapped into both address spaces | Zero; processes read and write the same pages | High-throughput data exchange: video frames, analytics engines, databases |
| Message queue (POSIX or System V) | Kernel-held queue of discrete messages, optionally with an order key | Two | Discrete messages without a broker |
| Memory-mapped file | A file mapped into memory by several processes | Zero after the page cache holds it | Sharing large read-mostly data; persistence for free |
| Signals | A tiny notification, no payload | n/a | Lifecycle events only (stop, reload) |
Choosing a mechanism
- 1
Default to sockets
A Unix domain socket for same-host communication is fast, bidirectional, supports many clients, works with every language, and switches to TCP with an address change if the processes later move apart. It is what Postgres, Docker and most local daemons use. - 2
Reach for shared memory when copies dominate
When you move hundreds of megabytes per second between processes, copies and system calls become the cost. Shared memory removes them, at the price of designing synchronisation (lock-free ring buffers, futexes) and handling a crashed peer that leaves shared state half-written. - 3
Use pipes for streams between related processes
Simple, ordered and back-pressured: a full pipe blocks the writer, which naturally throttles a fast producer. - 4
Use files and memory maps for large read-mostly data
Several processes mapping the same file share one copy in the page cache, which is how databases and search engines share index data across workers.
Structure on top of bytes
Pipes and stream sockets carry bytes, not messages. Something must define where one message ends: length prefixes, delimiters, or a protocol such as HTTP or gRPC over the socket. That framing and the serialisation of data into bytes often cost more than the IPC itself, which is why high-performance local paths use binary formats or shared memory with fixed layouts.
The sidecar case
A service mesh puts a proxy process next to every application, and every request goes app to proxy to network to proxy to app. Each local hop is IPC (usually loopback TCP or a Unix socket), adding tens to hundreds of microseconds and CPU for copying and parsing. That cost is small next to a network round trip, but it is why meshes increasingly move work into the kernel (eBPF) or into a per-node proxy instead of per-pod ones.
Trade-offs
Copying mechanisms are simple, safe and portable, and cost a copy and a system call per side. Shared memory is the fastest and hands you concurrency bugs and crash recovery. Sockets are the most flexible and carry the cost of framing and serialisation. The isolation that makes processes safe is exactly what makes them slower to talk to than threads.
In an interview
Use it for local architecture: "The app talks to its local cache daemon over a Unix domain socket, which avoids the TCP stack; if we split them onto different hosts we change the address to TCP and nothing else changes. The video pipeline passes frames through a shared-memory ring buffer because copying 4K frames between processes would dominate CPU."
Check yourself
A local analytics process receives 500 MB per second of event data from an ingest process on the same host over a loopback TCP connection, and profiling shows most CPU time in copying and system calls. What is the strongest fix?