Impact of Adjacent Variable Modification by Multiple Threads #
False sharing occurs when multiple threads modify adjacent data stored in memory, leading to performance degradation due to unnecessary cache invalidations. In modern multi-core systems, where cache coherence protocols are crucial, minimizing false sharing becomes critical for optimizing performance in multithreaded applications.
Cache Line Contention: Visual Representation #
Diagram for False Sharing in Adjacent Data #

In this diagram, Variable A and Variable B are stored in the same cache line, causing both cores to continuously invalidate each other’s cache when either variable is updated.
Code Examples: False Sharing in Independent and Struct Variables #
1. False Sharing with Independent Variables #
#include <iostream>
#include <thread>
const int NUM_ITER = 10000000;
int a = 0; // Modified by Thread 1
int b = 0; // Modified by Thread 2
void threadFunc1() {
for (int i = 0; i < NUM_ITER; ++i) {
a++;
}
}
void threadFunc2() {
for (int i = 0; i < NUM_ITER; ++i) {
b++;
}
}
int main() {
std::thread t1(threadFunc1);
std::thread t2(threadFunc2);
t1.join();
t2.join();
std::cout << "Final values: " << a << ", " << b << std::endl;
}
In this example, a and b are adjacent in memory, possibly on the same cache line. As thread 1 modifies a and thread 2 modifies b, the cache line containing both variables must be invalidated and reloaded, causing excessive cache coherence traffic and negatively affecting performance.
2. False Sharing with Struct Variables #
#include <iostream>
#include <thread>
const int NUM_THREADS = 2;
const int NUM_ITER = 10000000;
struct SharedData {
int a; // Modified by Thread 1
int b; // Modified by Thread 2
} data;
void threadFunc1() {
for (int i = 0; i < NUM_ITER; ++i) {
data.a++; // This will cause false sharing with data.b
}
}
void threadFunc2() {
for (int i = 0; i < NUM_ITER; ++i) {
data.b++; // This will cause false sharing with data.a
}
}
int main() {
std::thread t1(threadFunc1);
std::thread t2(threadFunc2);
t1.join();
t2.join();
std::cout << "Final values: " << data.a << ", " << data.b << std::endl;
}
Similarly, in this example, data.a and data.b are adjacent in memory, possibly on the same cache line. As thread 1 modifies data.a and thread 2 modifies data.b, the cache line containing both member variables must be invalidated and reloaded, causing excessive cache coherence traffic and negatively affecting performance.
Optimizing for Performance: Mitigating False Sharing #
1. Use Alignment to Separate Variables #
a. Stack or Global Variables with alignas
#
alignas(64) int a = 0;
alignas(64) int b = 0;
b. Heap Allocation with Alignment #
int* a = static_cast<int*>(std::aligned_alloc(64, 64));
int* b = static_cast<int*>(std::aligned_alloc(64, 64));
Note: std::aligned_alloc is available only in C++17 and later versions. If working with an earlier C++ version, consider using posix_memalign or similar alternatives for heap alignment.
Prevents automatic adjacent placement in memory.*
2. Use Padding to Separate Variables #
struct PaddedInt {
int value;
char padding[60]; // Assuming a 64-byte cache line
};
PaddedInt a;
PaddedInt b;
struct alignas(64) SharedData {
int a;
char padding[60]; // Padding to ensure a and b do not share the same cache line
int b;
};
Forces
aandbto be allocated in different cache lines, reducing contention.
3. Using Thread-Local Storage (thread_local)
#
thread_local int a;
thread_local int b;
Using thread_local ensures that each thread gets its own instance of a and b, stored in thread-local storage, which prevents false sharing as the variables are not shared between threads.
Key Takeaways #
- False sharing occurs when multiple threads modify adjacent variables that share the same cache line, leading to performance degradation.
- The problem is the same whether the variables are independent or part of a struct.
- Mitigation strategies include alignment, padding, splitting variables, and using thread-local storage.