Linux

Inter-Process & Inter-Thread Communication

The instant work is split across multiple processes or threads, they need a way to actually talk to each other — share data, signal that something happened, or protect a resource both are touching. Linux provides several mechanisms, each shaped for a different kind of problem: pipes, POSIX message queues, shared memory, semaphores, and mutexes.

Quick reference
What it is
The mechanisms Linux/POSIX provide for separate processes or threads to exchange data and coordinate access to shared state.
Mental model
A pipe is a hose; a message queue is a mailbox for discrete letters; shared memory is a whiteboard both sides can see directly; a semaphore/mutex is the talking stick that says whose turn it is.
Key numbers
Shared memory is the fastest option — no kernel copy for the actual data — but buys none of its own synchronization; pipes and message queues are safer by default at a real throughput cost.
Reach for it when
Splitting work across processes or threads that then need to exchange data or coordinate access to something they both touch.

Processes vs. threads: what's actually shared

This distinction decides which tool actually applies. A process has its own private address space by default — one process literally cannot read another's memory without an explicit IPC mechanism set up for exactly that purpose. Threads within one process already share the same address space — any global variable or heap allocation is directly visible to every thread in that process, with no setup required at all. That's why the "communication" problem is different for each: processes need an explicit channel just to exchange any data, while threads already share data automatically and instead need help coordinating safe access to it.

Two processes have entirely separate memory and need an explicit IPC channel to exchange anything; three threads inside one process already share the same memory automatically Processes — separate address spaces Process A private memory Process B private memory explicit IPC channel (pipe, queue, shared segment) Threads — shared address space one process Thread 1 Thread 2 Thread 3 shared memory heap and globals — automatic, no setup
Everything below "processes" needs a mechanism just to exchange data at all; everything below "threads" already has that — the remaining problem is purely coordination.

Pipes and named pipes (FIFOs)

An anonymous pipe (the pipe() syscall) is a unidirectional byte stream, typically shared between a parent process and a child it just fork()ed — exactly what a shell's | operator sets up between two commands. A named pipe (FIFO, created with mkfifo()) is the same idea with a filesystem path, so unrelated processes that never shared a common ancestor can still open and use it.

int fds[2];
pipe(fds);                    // fds[0] = read end, fds[1] = write end

pid_t pid = fork();
if (pid == 0) {
    close(fds[1]);             // child only reads
    char buf[64];
    read(fds[0], buf, sizeof(buf));
} else {
    close(fds[0]);             // parent only writes
    write(fds[1], "hello", 5);
}

POSIX message queues

A message queue moves discrete, labeled messages instead of a raw byte stream — each mq_send() preserves its own boundary and can carry a priority, and (unlike a pipe) the queue itself persists as a kernel object even while no process currently has it open.

mqd_t mq = mq_open("/sensor_readings", O_CREAT | O_WRONLY, 0644, NULL);
mq_send(mq, (const char *)&reading, sizeof(reading), /*priority=*/0);
mq_close(mq);

// ... in the reading process ...
mqd_t mq = mq_open("/sensor_readings", O_RDONLY);
struct sensor_reading reading;
mq_receive(mq, (char *)&reading, sizeof(reading), NULL);

Shared memory

Shared memory maps the same physical memory into two or more processes' address spaces — once set up, reading or writing it costs nothing extra beyond a normal memory access, with no data actually copied through the kernel the way a pipe or message queue requires. That speed is also exactly why it needs a separate synchronization mechanism: shared memory alone guarantees nothing about who's allowed to touch it when.

int fd = shm_open("/frame_buffer", O_CREAT | O_RDWR, 0644);
ftruncate(fd, BUFFER_SIZE);
void *addr = mmap(NULL, BUFFER_SIZE, PROT_READ | PROT_WRITE,
                   MAP_SHARED, fd, 0);
// `addr` now points at memory every process that mapped
// this same shared object can read and write directly.

Semaphores and mutexes

A semaphore is a counter: sem_wait() blocks until the count is above zero then decrements it, sem_post() increments it — usable between either processes or threads, and general enough to signal an event, not just protect a resource. A mutex (pthread_mutex_t) is more specific: it protects exactly one resource, has a notion of ownership, and is primarily a thread tool (though a pthread_mutex_t placed in shared memory with the PTHREAD_PROCESS_SHARED attribute can protect a cross-process shared-memory region too).

pthread_mutex_lock(&shared_data_mutex);
shared_data.value = new_value;      // safe: only one thread here at a time
pthread_mutex_unlock(&shared_data_mutex);

This is precisely the same shared-state hazard covered in Interrupts and RTOS — a mutex on Linux and a mutex inside an RTOS solve the exact same problem, just at a different layer of the stack.

Choosing between them

MechanismData modelBuilt-in sync?Scope
Pipe / FIFOByte stream, no message boundariesOrdering onlyProcesses
Message queueDiscrete, labeled, priority-ordered messagesMessage-level orderingProcesses
Shared memoryRaw shared bytesNone — you add itProcesses
SemaphoreA counterIs itself the primitiveProcesses or threads
MutexA lock with ownershipIs itself the primitiveThreads (usually)

Real-world applications

Gotchas

Field notes