x86 architecture
memory ordering
LFENCE instruction
SFENCE instruction
MFENCE instruction

When are x86 LFENCE, SFENCE and MFENCE instructions required?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

On x86, explicit fence instructions are needed less often than many developers think because the architecture already provides a relatively strong memory-ordering model for normal cached memory. The right answer depends on what kind of ordering you need: loads, stores, or both. In portable application code, you usually should not write raw fence instructions yourself and should rely on language atomics or compiler intrinsics instead.

The Short Mental Model

For normal write-back memory on x86 TSO, the architecture already preserves:

  • load-load order
  • load-store order
  • store-store order

The main reordering visible to other cores is store-load, due to store buffering.

That is why x86 often needs fewer explicit barriers than weaker architectures such as ARM.

What the Three Fence Instructions Mean

At a high level:

  • 'LFENCE orders prior loads before later loads'
  • 'SFENCE orders prior stores before later stores'
  • 'MFENCE is a full memory fence for both loads and stores'

In intrinsics form on MSVC or similar toolchains:

c
1#include <emmintrin.h>
2
3void use_fences(void) {
4    _mm_lfence();
5    _mm_sfence();
6    _mm_mfence();
7}

The question is not what they do in isolation, but when they are actually required.

When LFENCE Is Required

LFENCE is mostly relevant when you need to ensure that earlier loads are complete before later operations proceed, especially in low-level timing or speculation-sensitive code.

Typical uses:

  • serializing load-dependent timing sequences
  • ordering around instructions such as RDTSC in some measurement patterns
  • mitigation patterns related to speculation control in low-level code

Example timing pattern:

c
1#include <x86intrin.h>
2#include <stdio.h>
3
4int main(void) {
5    unsigned long long t1 = __rdtsc();
6    _mm_lfence();
7    unsigned long long t2 = __rdtsc();
8    printf("%llu\\n", t2 - t1);
9    return 0;
10}

For ordinary lock-protected application code, LFENCE is rarely something you insert manually.

When SFENCE Is Required

SFENCE matters most when stores are not already strongly ordered by the normal memory type behavior you rely on.

Typical cases:

  • non-temporal or streaming stores
  • write-combining memory regions
  • device or low-level systems code where store visibility order matters explicitly

Example with streaming stores:

c
1#include <emmintrin.h>
2
3void write_stream(int *dst, __m128i value) {
4    _mm_stream_si128((__m128i*)dst, value);
5    _mm_sfence();
6}

Without the fence, later code may observe the streaming-store sequence as incomplete at a point where you expected it to be globally visible.

When MFENCE Is Required

MFENCE is a full barrier and is the broadest tool of the three. It is used when you need both load and store ordering and cannot rely on another synchronizing instruction.

Typical cases:

  • very low-level lock-free code written directly in assembly or intrinsics
  • ordering around non-temporal memory operations
  • special memory-type interactions outside normal application code

Example:

c
1#include <emmintrin.h>
2
3void full_barrier(void) {
4    _mm_mfence();
5}

In practice, if you are asking whether to insert raw MFENCE in ordinary C or C++ multi-threaded code, the answer is usually no: use atomics instead.

Why Atomics Usually Matter More Than Raw Fences

In C++, C#, Java, and Rust, you normally express synchronization through language-level atomic or lock primitives. The compiler and runtime then generate the right machine instructions for the target architecture.

For example, in C++:

cpp
1#include <atomic>
2
3std::atomic<int> flag = 0;
4
5void publish() {
6    flag.store(1, std::memory_order_release);
7}
8
9void consume() {
10    while (flag.load(std::memory_order_acquire) == 0) {
11    }
12}

On x86, acquire and release semantics are often satisfied without explicit fence instructions because the architecture is already strong enough for those operations on normal memory.

That is a feature, not a weakness. It means you should write portable synchronization semantics and let the toolchain map them correctly.

Locked Instructions Already Imply Ordering

Many atomic read-modify-write instructions on x86 are generated as locked operations, and those already provide strong ordering. That is another reason manual fences are often redundant in high-level code.

If your code already uses:

  • mutexes
  • atomics
  • compare-and-swap
  • lock-prefixed instructions

then adding raw fences manually is often unnecessary or wrong.

Cases Where Manual Fences Are Usually Not Needed

For ordinary application code on x86, you usually do not need LFENCE, SFENCE, or MFENCE just to:

  • protect shared data with a mutex
  • use normal acquire-release atomics
  • publish simple state transitions in standard lock-free patterns

If you think you need them there, re-check whether the real problem is missing atomic semantics or a data race.

Common Pitfalls

One common mistake is assuming that every memory-ordering problem should be solved with a raw fence instruction. In high-level code, atomics are usually the correct tool.

Another mistake is ignoring memory type. Fences become much more relevant with streaming stores or non-standard memory regions.

Developers also mix compiler barriers and CPU fences as if they were the same thing. They are not.

Finally, many explanations oversimplify x86 as "strong enough so fences never matter." They do matter in low-level and non-temporal cases, just less often than on weaker architectures.

Summary

  • x86 already provides strong ordering for normal cached memory under TSO.
  • 'LFENCE is mainly for load ordering and timing or speculation-sensitive cases.'
  • 'SFENCE is mainly for ordering stores, especially streaming or weakly ordered stores.'
  • 'MFENCE is a full barrier but is rarely needed directly in ordinary application code.'
  • In portable code, prefer language atomics and let the compiler generate the right instructions.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.