Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Quant developer HFT course · Module 6: The operating system

Processes, threads and shared memory

Lesson · 10 min

The question

The feed handler and the strategy are separate processes so that a strategy crash cannot take down the feed. How should they talk to each other, and what does it cost?

Processes or threads

Threads share one address space: passing data is a pointer and a queue, and a crash or memory corruption in one takes down all of them. Processes have separate address spaces: a crash in one leaves the others running, they can be restarted and deployed independently, and one can be written in a different language, but passing data needs an inter-process mechanism.

Trading systems use both. A common shape is one process per function (market-data handling, each strategy, order gateway, risk), with several pinned threads inside each, communicating through shared memory. The question's design reflects that.

The mechanisms and their costs

MechanismPath per messageTypical use
Pipe, Unix domain socket, TCP over loopbackwrite and read system calls, a copy into and out of the kernel, possibly a wake-upcontrol messages, anything not latency-critical
Message queues (POSIX, System V)system calls and kernel copiesrare in trading
Signalsasynchronous interrupt of the receivernever for data
Shared memoryordinary loads and stores to a region mapped into both processesthe hot path

Every mechanism that goes through the kernel costs at least two system calls and two copies per message, plus a wake-up if the reader sleeps. That is microseconds. Shared memory costs what a cache line transfer between cores costs.

Shared memory with a ring buffer

Both processes map the same physical pages, so a data structure placed in that region is visible to both. The SPSC ring from Module 4 works unchanged, as long as it uses only atomics and plain data and contains no pointers.

writesreadsfeed processstrategy processshared mappingring buffersame physicalpages
Shared memory: two processes map the same physical pages, so a lock-free ring buffer works between them exactly as between threads, with no system call per message.
// both processes
int fd = shm_open("/md_to_strat", O_CREAT | O_RDWR, 0600);
ftruncate(fd, sizeof(Ring));
void* p = mmap(nullptr, sizeof(Ring), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0);
// creator only: placement-new the ring so its atomics are constructed once
Ring* ring = static_cast<Ring*>(p);

Rules for structures in shared memory:

  • No pointers. The region may be mapped at different addresses in each process. Use offsets or indices.
  • Only lock-free atomics, and check std::atomic<T>::is_always_lock_free; an atomic implemented with a hidden lock would put the lock in one process's memory.
  • Trivially copyable, fixed-layout data, checked with static_assert on sizes, since the two sides may be built separately.
  • A version and layout check in a header at the start of the region, so a reader built against an old layout refuses to attach.
  • Huge pages and pre-faulting for large regions, as in the previous lessons.

What happens when one side dies

Shared memory makes crash isolation partial. If the strategy dies, the ring keeps its state, the feed keeps writing, and the ring fills; the feed must treat a full ring as "consumer gone or slow" and drop rather than block. If the feed dies, the strategy sees no new data and needs a heartbeat to tell silence from a dead producer. A restarted process must be able to attach to an existing region and resume, or start from a clean one. A mutex in shared memory is particularly dangerous: if a process dies holding it, the others wait for ever unless it is a robust mutex (PTHREAD_MUTEX_ROBUST), which is one more reason the hot path uses lock-free structures.

Many readers

Market data is usually consumed by several strategies. Rather than one ring per reader written by the feed, a common design is a single broadcast ring: the feed writes each message once, each reader keeps its own read position, and the writer never waits for readers. A reader that falls a full lap behind detects that its position has been overwritten (from a sequence number in each slot) and resynchronises from a snapshot. This is the same idea as the seqlock, applied to a log of messages.

Check yourself

Question 1 of 3

Why is a Unix domain socket slower than shared memory for passing each message?

Question 2 of 3

Why must a structure placed in shared memory avoid raw pointers?

Question 3 of 3

The consumer process crashes. What should the producer writing a shared ring do?

In the interview

Say your own answer out loud first, then open the one we would give.

When would you use separate processes rather than threads?

When I want fault isolation, independent deployment and restart, or different languages: a strategy crash should not take down market data or the gateway. Threads are simpler when components must share a lot of state and fail together anyway. Trading systems often use both: processes per function, pinned threads inside.

What is the fastest way for two processes on one machine to exchange messages?

Shared memory with a lock-free ring buffer: both processes map the same pages and exchange data with ordinary loads and stores, so there are no system calls or kernel copies per message. The structure must use offsets instead of pointers, lock-free atomics and a fixed, versioned layout.

What are the risks of shared memory IPC?

Layout mismatches between separately built processes, which a version header and static asserts guard against; pointers that are meaningless in the other process; locks held by a process that dies; and needing heartbeats to detect a dead peer. Lock-free structures and a producer that never waits on consumers keep failures contained.