Thread_local in C++
In this example, you will learn about the thread_local in C++ with its examples.
Introduction:
Thread-local variables are declared in C++ using the thread_local keyword. A thread-local variable has its own instance for every thread in a multi-threaded program. This implies that each thread is permitted to have an independent copy of the variable, and that changes made to a variable in one thread do not affect its value in other threads.
Thread-Local Storage: It's common for multi-threaded C++ programs to require variables that are unique to every thread. A feature of C++ 11 called thread local storage (TLS) enables an individual instance of a variable to be maintained by each thread in a multi-threaded program. In other terms, we may say that every thread is capable of having a separate, autonomous instance of a variable. There is no inter-thread interference when a thread accesses and modifies its own copy of the variable.
- Declaration: The thread_local keyword must come before the variable's type to declare a thread-local variable.
For example: thread_local int threadSpecificValue = 0;
The threadSpecificValue integer variable, which has an initial value of 0, is declared in this way.
- Scope: This thread is a type of thread in which a thread_local variable is defined. As a result, modifications made to the variable are limited to that particular thread, and each thread has its own independent copy of the variable.
- Thread lifetime: The thread_local variable is present for the whole thread's runtime. The thread_local variables of an instance are created upon thread creation and destructed upon thread exit.
- Thread Safety: It's crucial to understand that thread_local does not inherently provide thread safety to the code. It just offers a variable thread-local storage. When using shared resources, you still have to make sure that everything is synchronized properly.
Example:
A C++ Code for Thread_local storage:
#include <iostream> #include <thread> // Declare a thread-local variable thread_local int threadSpecificValue = 0; // Function that increments and prints the thread-local variable void ThreadFunction() { // Increment the thread-local variable threadSpecificValue++; // Print the value in this thread std::cout << "Thread ID: " << std::this_thread::get_id() << " - Thread-Specific Value: " << threadSpecificValue << std::endl; } int main() { // Create two threads std::thread thread1(ThreadFunction); std::thread thread2(ThreadFunction); // Wait for the threads to finish thread1.join(); thread2.join(); // Main thread's access to the thread-local variable std::cout << "Main Thread ID: " << std::this_thread::get_id() << " - Thread-Specific Value: " << threadSpecificValue << std::endl; return 0;
Output:
Thread ID: 12345 - Thread-Specific Value: 1 Thread ID: 67890 - Thread-Specific Value: 1 Main Thread ID: 23456 - Thread-Specific Value: 0
Explanation:
This program declares and initializes a thread_local variable called threadSpecificValue to 0. After that, the threadSpecificValue variable is incremented and printed by the ThreadFunction, which is called by each of the two threads that we have created. Ultimately, the threadSpecificValue variable is accessed and printed by the main thread as well. As the result shows, you can see that every thread has a copy of threadSpecificValue and that modifications are limited to that thread.
In the above output, the new number is printed by two threads (Thread 1 and Thread 2) after they each increase their own threadSpecificValue variable by 1.
Accessing its own threadSpecificValue with a distinct thread ID, the main thread leaves it at 0, as the other threads did not change it.
By using singleton:
C++ Code:
// C++ Program to demonstrate the use of thread-local // storage. #include <iostream> #include <thread> using namespace std; class Singleton { public: static Singleton& getInstance() { // Each thread will have its own instance of // Singleton thread_local Singleton instance; return instance; } void printMessage() { cout << "Hello from thread "<< this_thread::get_id() << endl; } private: Singleton() = default; }; void workerThread() { Singleton::getInstance().printMessage(); } int main() { // Create first thread thread t1(workerThread); // Create second thread thread t2(workerThread); // Wait for the first thread to finish t1.join(); // Wait for the second thread to finish t2.join(); return 0; }
Output:
Hello from thread 140544107624704 Hello from thread 140544099232000
Explanation:
- T1 and T2 are the names of the two worker threads produced.
- The workerThread function is called by each thread, and it calls Singleton after that.findInstance(). Use the printMessage() function to print the thread's ID in a message.
- Two lines of output will appear, each with a distinct thread ID because each thread has its own instance of the Singleton. It proves that a single isolated instance of the Singleton class exists for every thread.
Conclusion:
In conclusion, C++'s thread_local keyword is a useful tool for handling thread-specific data in multi-threaded applications. It efficiently isolates data and lowers the chance of data races by enabling variables to have a separate instance for every thread. It helps to improve speed by removing the need for explicit synchronization and is especially helpful in situations when thread-local state or configuration settings are needed. It's crucial to remember that thread_local does not automatically turn a program into one that is thread-safe. Although thread_local makes it easier to isolate thread-specific data, developers still need to exercise caution when managing concurrent access to shared resources. It makes C++ multi-threaded programming more reliable and effective.