Concurrency Programming (11): Shared Memory and Message Passing — How They Work Together

Explores how channels and queues synchronize shared state internally, and how shared memory and message passing work together in a task-processing system.

EnglishPublished 2 min read
Concurrency Programming (11): Shared Memory and Message Passing — How They Work Together

Table of Contents


0. Picking Up Where We Left Off

The preceding articles examined two concurrency models: Shared Memory and Message Passing.

We can now look at how they relate, and how to choose and combine them for a given concurrency problem.

With shared memory, execution units cooperate by accessing the same state. With message passing, they cooperate by sending and receiving messages. These are different ways of organizing work, not mutually exclusive approaches.

As we saw in the previous article, producers and consumers can exchange messages through a queue or channel even though the communication component itself relies on shared state.

Consider Java’s ArrayBlockingQueue:

Producer
    |
    v
ArrayBlockingQueue
    |
    +-- Array: stores messages
    |
    +-- ReentrantLock: protects queue state
    |
    +-- Condition: waits when empty or full
    |
    v
Consumer

Multiple producers and consumers may enqueue or dequeue concurrently. ArrayBlockingQueue therefore protects queue operations with a lock and uses conditions to coordinate waiting and wakeups when the queue is empty or full.

Go channels use the runtime’s hchan structure to manage buffers and waiting senders and receivers, coordinating access with locks, atomic operations, and goroutine scheduling. CPython’s queue.Queue uses a mutex and condition variables to protect its queue state.

Message passing does not eliminate shared state inside the implementation. Instead, channels and queues encapsulate the synchronization required for message handoffs. That is why our investigation of message passing led back to mutexes, atomics, conditions, and memory ordering: they help implement the atomicity, visibility, and ordering guarantees of message handoffs.

2. How Can They Work Together?

Real programs often use both models.

Suppose a task-processing system has multiple producers submitting work, multiple workers processing tasks in parallel, and shared counters tracking successes and failures:

Producer A ──┐
             ├──> Task Queue ──> Worker 1 ──┐
Producer B ──┘                └─> Worker 2 ──┤
                                            |
                                            v
                                     Shared Statistics
                                     (Mutex / Atomic)

Here, message passing handles task handoffs: producers enqueue tasks and workers dequeue and execute them. Shared memory handles statistics: workers update the same counters, using a mutex or atomic operations to synchronize access.

Alternatively, workers can send their results to a dedicated owner:

Producer --> Task Queue --> Workers
                               |
                               v
                          Result Queue
                               |
                               v
                          Stats Owner
                               |
                               v
                           Statistics

Now only the Stats Owner modifies the statistics; workers no longer access that state directly.

Both designs can be correct, but the trade-offs differ. For this example, updating shared state directly is simpler. Routing updates through a single owner can make multi-field invariants easier to manage, at the cost of an additional handoff, owner lifecycle management, and a potential serial bottleneck.

3. Closing Thoughts

This article examined how shared memory and message passing relate at the implementation level and how they can be combined in practice.

The next article is the final installment. We will look back over the entire concurrency programming series and bring it to a close with a summary of the key ideas.

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub