Thread Safety Explained: Private vs Shared Resources and Practical Guidelines
This article demystifies thread safety by comparing private and shared resources, defining when code is thread‑safe, illustrating common pitfalls with C/C++ examples, and presenting practical techniques such as using thread‑local storage, read‑only globals, atomic operations, and synchronization to write reliable multithreaded programs.
Many developers find multithreaded code intimidating, likening it to an untamable monster; the root of this difficulty is a misunderstanding of the concept of thread safety.
Thread safety means that a piece of code produces correct results when executed concurrently by multiple threads. Non‑thread‑safe code behaves nondeterministically, as if its outcome were decided by a dice roll.
Private vs. shared resources
Think of a thread as a person in a private house (its own resources) versus a public place (shared resources). In a private house, using personal items does not affect anyone else, which corresponds to thread‑private resources such as the stack. In a public place, resources like the heap, global variables, and opened files must be accessed in an orderly fashion (e.g., by queuing) to avoid conflicts.
The two fundamental cases are:
Thread‑private resources – no thread‑safety issues.
Shared resources – threads must coordinate to maintain safety.
When is code thread‑safe?
Code that only uses thread‑private data (e.g., local variables on the stack) is inherently thread‑safe. For example:
int func() {
int a = 1;
int b = 1;
return a + b;
}This function returns 2 regardless of how many threads call it.
Passing arguments by value also yields thread‑safe code because the copies are private to each thread:
int func(int num) {
num++;
return num;
}However, passing pointers or references can introduce shared data. If the pointer refers to a global variable, the function becomes unsafe unless protected:
int global_num = 1;
void thread1() { func(&global_num); }
void thread2() { func(&global_num); }In this case, func must be guarded by a lock or other synchronization mechanism.
Heap‑allocated objects are also shared resources; two threads using the same heap object will conflict unless each thread works with its own instance.
Read‑only globals and thread‑local storage
If a global variable is initialized once and never modified, it can be safely read by any thread:
int global_num = 100; // initialized once
int func() { return global_num; }Conversely, making a global variable thread‑local (e.g., using __thread in GCC) gives each thread its own copy, restoring safety:
__thread int global_num = 100;
int func() {
++global_num;
return global_num;
}Returning references
Returning a reference to a static variable creates a shared resource and is unsafe unless the variable is immutable or protected:
int* func() {
static int a = 100;
return &a
}This pattern is acceptable for implementing the Singleton design pattern, where the static instance is created once and then only read:
class S {
public:
static S& getInstance() {
static S instance;
return instance;
}
private:
S() {}
};Protecting non‑thread‑safe code
Even if a function itself is not thread‑safe, callers can make it safe by surrounding it with synchronization primitives:
mutex l;
int funcA() {
l.lock();
func();
l.unlock();
}Similarly, using a thread‑private local variable as the argument keeps the call safe:
void funcA() {
int a = 100;
func(&a);
}Practical measures to achieve thread safety
Avoid global mutable state; prefer stateless functions.
Use thread‑local storage for data that must be global but private per thread.
Make shared resources read‑only whenever possible.
Leverage atomic types (e.g., std::atomic ) for lock‑free operations.
Apply explicit synchronization (mutexes, semaphores) when mutable shared state is unavoidable.
In summary, writing thread‑safe code revolves around correctly identifying private versus shared resources and applying the appropriate strategy—whether that is using only private data, thread‑local storage, read‑only globals, atomic operations, or proper synchronization.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.