thread_local int i = 0;
When you declare a variable as thread_local, each thread will get its own copy. When you attempt to use it, the copy associated with the current thread is used.
std::cout << i;
If you take the address of a thread_local variable, it’s just a normal pointer, which can be passed between threads.
In case this happens, you need to take care that the pointer will not be accessed after the thread it belongs to has exited – otherwise you get a dangling pointer and undefined behaviour, as expected in such cases.
An example of utility of a thread_local object would be where functions use a global variable to set an error condition (such as errno).
This way, receiving an error on a thread will not impact other errors from other threads.
Another use case is when multiple threads accumulate some information into a global variable. To avoid a race condition and limit the usage of a mutex, each thread can accumulate their own information into the thread_local object, synchronizing them with the global variable only at the end.