memory barrier
interlocked operations
cache coherency
memory management
performance optimization

Memory barrier vs Interlocked impact on memory caches coherency timing

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Memory barriers and Interlocked operations are related concurrency tools, but they solve different problems. A barrier constrains ordering and visibility. Interlocked performs an atomic read-modify-write with ordering guarantees around that operation. If you are tuning multithreaded code, you need to separate correctness from timing cost, because the most expensive part is often not the fence itself but cache-line contention.

Ordering, Visibility, and Atomicity Are Different Concepts

These three ideas are often mixed together.

  • Ordering means whether operations can move before or after one another.
  • Visibility means when another core can observe a write.
  • Atomicity means whether an update happens as one indivisible step.

Cache coherency protocols keep cores from diverging forever on shared cache lines, but they do not replace language-level synchronization. You still need memory-model primitives to express safe cross-thread behavior.

A Memory Barrier Is About Ordering

A memory barrier does not magically make a compound update atomic. It creates a boundary that prevents certain reordering across that point.

csharp
1using System;
2using System.Threading;
3
4public static class BarrierDemo
5{
6    private static int _payload;
7    private static int _ready;
8
9    public static void Producer()
10    {
11        _payload = 123;
12        Thread.MemoryBarrier();
13        _ready = 1;
14    }
15
16    public static void Consumer()
17    {
18        while (_ready == 0)
19        {
20            Thread.SpinWait(20);
21        }
22
23        Thread.MemoryBarrier();
24        Console.WriteLine(_payload);
25    }
26}

This is a publication pattern. The barrier helps ensure the payload write is visible before the ready flag is observed. It does not turn arbitrary multi-variable updates into a transaction.

In modern C#, Volatile.Read and Volatile.Write are often clearer than raw barriers for simple publication patterns.

Interlocked Is About Atomic Mutation

Interlocked is the right tool when a shared variable must be updated atomically.

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5public static class InterlockedDemo
6{
7    private static int _counter;
8
9    public static async Task Main()
10    {
11        var workers = new Task[4];
12        for (int i = 0; i < workers.Length; i++)
13        {
14            workers[i] = Task.Run(() =>
15            {
16                for (int j = 0; j < 200_000; j++)
17                {
18                    Interlocked.Increment(ref _counter);
19                }
20            });
21        }
22
23        await Task.WhenAll(workers);
24        Console.WriteLine(_counter);
25    }
26}

If you replace Interlocked.Increment with a plain increment, lost updates are likely under contention. The barrier alone would not fix that.

Cache-Line Contention Often Dominates Timing

The common performance problem is not “memory barrier versus interlocked” in isolation. It is many cores fighting over one hot cache line.

When several threads repeatedly update the same location:

  • cache-line ownership moves between cores,
  • coherence traffic increases,
  • throughput flattens,
  • and tail latency worsens.

That effect is often called cache-line ping-pong. It is why a correct atomic counter can still scale poorly under heavy contention.

Reduce Contention Before Micro-Optimizing Fences

If the workload pounds one shared variable, redesigning the data layout is usually more effective than swapping primitives.

csharp
1using System;
2using System.Threading;
3
4public sealed class ShardedCounter
5{
6    private readonly long[] _slots;
7
8    public ShardedCounter(int slots)
9    {
10        _slots = new long[slots];
11    }
12
13    public void Increment()
14    {
15        int idx = Thread.GetCurrentProcessorId() % _slots.Length;
16        Interlocked.Increment(ref _slots[idx]);
17    }
18
19    public long Sum()
20    {
21        long total = 0;
22        for (int i = 0; i < _slots.Length; i++)
23        {
24            total += Volatile.Read(ref _slots[i]);
25        }
26        return total;
27    }
28}

This spreads writes across several cache lines and usually scales better than one globally contended counter.

Choose the Primitive by Correctness First

A good rule is:

  • use barriers or volatile operations for publication and visibility patterns,
  • use Interlocked for atomic updates to one variable,
  • use locks when multiple fields must change together under one invariant.

A lock is not automatically “slower” in every real program. For multi-field state, it is often the simplest and safest design.

Benchmark with Real Contention Patterns

Do not benchmark these primitives only in a single-threaded loop. That tells you almost nothing about the workload shape that actually matters.

A useful benchmark matrix includes:

  1. single-thread baseline,
  2. moderate contention,
  3. saturated core count,
  4. realistic read-write ratios.

Measure throughput, latency, and CPU cost. Otherwise you may optimize the wrong aspect of the synchronization path.

Common Pitfalls

  • Treating a memory barrier as if it made non-atomic read-modify-write code safe.
  • Replacing every shared update with Interlocked and creating a hot-spot bottleneck.
  • Confusing hardware cache coherency with language-level synchronization guarantees.
  • Writing lock-free code for multi-field invariants that really need a lock.
  • Benchmarking synchronization primitives without realistic contention.

Summary

  • Memory barriers and Interlocked solve different concurrency problems.
  • Barriers help with ordering and visibility, not with atomic compound updates.
  • 'Interlocked provides atomic mutation, but heavy contention can make it expensive.'
  • Cache-line ownership bouncing is often the main timing bottleneck.
  • Pick the primitive for correctness first, then optimize based on real contention measurements.

Course illustration
Course illustration

All Rights Reserved.