C++ thread_local

In this article, you will learn about the thread_local in C++ with its syntax and examples.

What is the thread_local?

The thread_local keyword allows you to declare variables with thread-local storage duration. It means that each thread accessing the variable will get its copy of that variable.

Syntax:

It has the following syntax:

Here, each thread will have its copy of the variable var. If one thread modifies var, it will not affect the value of var in 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.

The thread_local keyword is valuable when you want a variable to be private to each thread rather than shared between all threads. It avoids explicitly passing the variable between threads and protects against race conditions around access.

Everyday use cases for thread_local include per-thread caches, per-thread random number generators, logging, metrics collectors specific to individual threads, etc.

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 have a lifetime tied to the thread where they are initialized upon first access. Their visibility is thread-specific, with each thread getting its copy. The scope depends on the context, and it could be restricted to a block, shared within a class, or available globally after declaration.

Examples of C++ thread_local

Example 1:

Let's take an example to illustrate the use of Thread_local Storage in C++.

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 us take a C++ program that shows how to use thread-local storage.

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 thread_local keyword allows for variables to be declared so that each thread accessing the variable gets its copy. It allows multiple threads to use the same variable without having to explicitly pass around the data between threads. It also avoids race conditions and complexity by locking shared data. Each thread copy is allocated upon first access and freed when the thread terminates. For static thread-local variables, this per-thread allocation happens only once on first access, with the storage persisting for the program's life. However, like with automatic thread_local variables, each thread still gets its private copy. It makes thread_local useful for encapsulating thread-specific data like caches, loggers, user context, etc. However, care needs to be taken if accessing the thread_local variable via a pointer instead of directly. The pointer needs to be appropriately managed according to per-thread lifetimes. The isolation and fast access of thread-local storage come at the cost of more memory usage by having separate copies for each thread.

Example of static local storage

Example 1:

Let's take an example to illustrate the use of static thread_local storage in C++.

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++:

  1. 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.
  2. Dynamic Initialization - thread_local variables are initialized dynamically on their first access by a thread. Their constructors cannot rely on other thread_local variables already being initialized.
  3. 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.
  4. 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.
  5. Platform Support - Some compilers may not fully support thread_local variables, especially some older compilers. Check compatibility for your target platforms.
  6. 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.
  7. Linkage Restriction - thread_local cannot be used on variables declared with the extern keyword. It is incompatible with external linkage.
  8. Type Restriction - thread_local cannot be applied to reference types in C++. It only works with object types and primitive types.

In summary, thread_local is very useful, but some limitations around the order of destruction, Initialization, overhead, and scope support must be considered during use.






Latest Courses