In this guide, you will explore the thread_local keyword in C++ along with its syntax and illustrations.
What is the thread_local?
The thread_local keyword enables the declaration of variables with thread-local storage duration. This indicates that each individual thread that accesses the variable will receive its own unique copy of that variable.
Syntax:
It has the following syntax:
thread_local int var = 0;
Here, every thread will possess its own version of the variable var. If a single thread alters var, it will have no impact on the value of var in any other threads.
Some key points about thread_local variables:
- They are initialized when the thread accesses them, not at program startup.
- They will be destroyed when the thread exits.
- Static thread-local variables behave like standard static variables that all threads can access, but each thread gets a separate copy.
- thread_local can be applied to static and non-static variables.
- thread_local variables can have complex types like classes. The individual copies are initialized using the default constructor.
Using the thread_local keyword is beneficial when you require a variable to be unique to each thread instead of being shared among all threads. This approach eliminates the need to pass the variable explicitly between threads and serves as a safeguard against race conditions during access operations.
Common scenarios where the thread_local keyword is applied consist of per-thread caching mechanisms, individual thread-based random number generators, logging functionalities, thread-specific metrics gathering, and so on.
What are the properties of thread local storage?
Here are some of the main properties and behaviours of thread local storage (TLS) in C++:
- Lifetime - It begins when first accessed by a thread and ends when that thread terminates.
- Visibility - Each thread gets its separate copy of the variable.
- Scope:
- Namespace scope - It is accessible throughout code after declaration.
- Class scope - It is accessible to all member functions.
- Function/block scope - It is accessible within that code block.
Thread_local variables are associated with the lifespan of the thread in which they are first accessed. They are unique to each thread, ensuring thread-specific visibility. The extent of their scope varies based on the situation, ranging from being limited to a specific block, accessible within a class, or globally available post declaration.
Examples of C++ thread_local
Example 1:
Let's consider a scenario to demonstrate the utilization of Thread_local Storage in C++.
#include <iostream>
#include <thread>
// thread_local variable declaration
thread_local int threadLocalVariable = 0;
// Function to demonstrate the use of thread_local variable
void threadFunction() {
// Increment and print thread_local variable
threadLocalVariable++;
std::cout << "Thread ID: " << std::this_thread::get_id()
<< " - Thread Local Variable: " << threadLocalVariable << std::endl;
}
int main() {
// Creating two threads
std::thread t1(threadFunction);
std::thread t2(threadFunction);
// Waiting for threads to finish
t1.join();
t2.join();
// Main thread prints its thread_local variable
std::cout << "Main Thread ID: " << std::this_thread::get_id()
<< " - Thread Local Variable: " << threadLocalVariable << std::endl;
return 0;
}
Output:
Thread ID: 12345 - Thread Local Variable: 1
Thread ID: 67890 - Thread Local Variable: 1
Main Thread ID: 98765 - Thread Local Variable: 0
Example 2:
Let's consider a C++ code example demonstrating the implementation of thread-local storage.
#include <iostream>
#include <thread>
// thread_local variable declaration
thread_local int threadLocalVariable = 0;
// Function to demonstrate the use of thread_local variable
void threadFunction() {
// Increment and print thread_local variable
threadLocalVariable++;
std::cout << "Thread ID: " << std::this_thread::get_id()
<< " - Thread Local Variable: " << threadLocalVariable << std::endl;
}
int main() {
// Creating two threads
std::thread t1(threadFunction);
std::thread t2(threadFunction);
// Waiting for threads to finish
t1.join();
t2.join();
// Main thread prints its thread_local variable
std::cout << "Main Thread ID: " << std::this_thread::get_id()
<< " - Thread Local Variable: " << threadLocalVariable << std::endl;
return 0;
}
Output:
Thread ID: 12345 - Thread Local Variable: 1
Thread ID: 67890 - Thread Local Variable: 1
Main Thread ID: 98765 - Thread Local Variable: 0
Static Thread Local Storage
The threadlocal keyword enables the declaration of variables so that each thread accessing the variable receives its individual copy. This feature facilitates multiple threads utilizing the same variable without the need to explicitly transfer data among threads. It serves to prevent race conditions and intricacies by synchronizing shared data. The allocation of each thread's copy occurs upon initial access and is deallocated upon the thread's termination. For static thread-local variables, this per-thread allocation is a one-time event on the first access, with the memory persisting throughout the program's lifespan. Nonetheless, similar to automatic threadlocal variables, each thread still obtains its distinct private copy. This feature makes threadlocal beneficial for encapsulating thread-specific information such as caches, loggers, and user context. However, caution must be exercised when accessing the threadlocal variable through a pointer rather than directly. Proper management of the pointer is essential to align with per-thread lifecycles. While the isolation and rapid access of thread-local storage are advantageous, they result in increased memory consumption due to maintaining separate copies for each thread.
Example of static local storage
Example 1:
Let's consider an example to demonstrate the utilization of static thread_local storage in C++.
#include <iostream>
#include <thread>
// static thread_local variable declaration
static thread_local int staticThreadLocalVariable = 0;
// Function to demonstrate the use of static thread_local variable
void threadFunction() {
// Increment and print static thread_local variable
staticThreadLocalVariable++;
std::cout << "Thread ID: " << std::this_thread::get_id()
<< " - Static Thread Local Variable: " << staticThreadLocalVariable << std::endl;
}
int main() {
// Creating two threads
std::thread t1(threadFunction);
std::thread t2(threadFunction);
// Waiting for threads to finish
t1.join();
t2.join();
// Main thread prints its static thread_local variable
std::cout << "Main Thread ID: " << std::this_thread::get_id()
<< " - Static Thread Local Variable: " << staticThreadLocalVariable << std::endl;
return 0;
}
Output:
Thread ID: 12345 - Static Thread Local Variable: 1
Thread ID: 67890 - Static Thread Local Variable: 1
Main Thread ID: 98765 - Static Thread Local Variable: 0
Rules and Limitations of C++ thread_local
Here are some fundamental rules and limitations to keep in mind when using thread_local in C++:
- Order of Destruction - The order in which thread_local objects are destroyed when a thread exits is unspecified in the C++ standard. Do not rely on a specific destruction order for thread-local things.
- Dynamic Initialization - threadlocal variables are initialized dynamically on their first access by a thread. Their constructors cannot rely on other threadlocal variables already being initialized.
- Performance Overhead - Having per-thread copies of variables has a memory overhead cost that grows with more threads. Cache performance may also be negatively impacted.
- Private by Default - thread_local introduces a private scope to each variable for each thread by Default. The data cannot be directly shared between threads without explicit passing.
- Platform Support - Some compilers may not fully support thread_local variables, especially some older compilers. Check compatibility for your target platforms.
- Scope Limitation - thread_local only applies to variables declared at namespace, class, and block scope. You cannot use it on function parameters or method return types.
- Linkage Restriction - thread_local cannot be used on variables declared with the extern keyword. It is incompatible with external linkage.
- Type Restriction - thread_local cannot be applied to reference types in C++. It only works with object types and primitive types.
In essence, the thread_local keyword offers significant utility, yet users should be mindful of constraints related to destruction sequence, initialization, performance impact, and scope compatibility when employing it.