Is it allowed to leak pointers of thread local variable?

Viewed 290

I am currently trying to better understand thread local storage in C++. To this end I wrote the following example:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>

thread_local int thread_index;

std::vector<int*> pointers;
std::mutex mutex;

void show_index()
{
  {
    thread_index = 0;

    std::lock_guard<std::mutex> lk(mutex);

    pointers.push_back(&thread_index);
  }

  while(true)
  {
    std::this_thread::sleep_for(std::chrono::seconds(1));

    {
      std::lock_guard<std::mutex> lk(mutex);
      std::cout << "Index of thread "
                << std::this_thread::get_id()
                << " : "
                << thread_index
                << std::endl;
    }
  }
}

void change_index()
{
  while(true)
  {
    std::this_thread::sleep_for(std::chrono::seconds(3));

    {
      std::lock_guard<std::mutex> lk(mutex);

      std::cout << "Thread "
                << std::this_thread::get_id()
                << " is changing indices"
                << std::endl;

      for(auto& pointer : pointers)
      {
        ++(*pointer);
      }
    }
  }
}

int main()
{
  std::vector<std::thread> threads;

  for(int i = 0; i < 3; ++i)
  {
    threads.push_back(std::thread(show_index));
  }

  threads.push_back(std::thread(change_index));

  for(auto& thread : threads)
  {
    thread.join();
  }

  return 0;
}

As can be seen the address of a thread_local variable is leaked into the program and the thread local variables are inadvertedly changed by another thread. All access to the global state is protected by a std::mutex, so there should not be an races. Indeed, I see the following expected output:

Index of thread 140443980949056 : 0
Index of thread 140443887072832 : 0
Index of thread 140443989341760 : 0
Index of thread 140443980949056 : 0
Index of thread 140443887072832 : 0
Index of thread 140443989341760 : 0
Thread 140443972556352 is changing indices
Index of thread 140443980949056 : 1
Index of thread 140443989341760 : 1
Index of thread 140443887072832 : 1
...

My question is: What does the standard say about accessing thread local variables from another thread? Is a thread local variable merely a pointer to memory that is guaranteed to be unique for each thread, or does the standard mandate "thread local" access as well?

2 Answers

It's precisely the same as any other local object. All threads share the same view of memory and a function or thread can pass a pointer to a local object to another thread or function if it wants to. If you do this, it's you responsibility to ensure there are no race conditions and an object isn't accessed outside of its lifetime.

I disagree that anything is "inadvertently" changed here. Threads have the same view of memory, so a pointer to an object in one thread will point to the very same object in another thread during that object's lifetime.

The situation is the same with objects on a thread's stack. They are local in the sense that each thread has its own stack. But it is perfectly legal for one thread to give another thread a pointer or reference to an object on its stack. Thread safety and lifetime issues are, as always, your responsbility.

Thread local access is not required. However, there is a subtlety you want to be aware of with your example code:

Data in thread local storage is deleted when the owning thread exits. This means that the posted code is not guaranteed to work. If a show_index thread exits before the change_index thread the data accesses in change_index might be invalid and that pointer may be pointing to garbage or another threads TLS. Because the threads involve infinite loops, they will never terminate on their own. However, when you kill the program show_index thread may be killed right before a change_index thread reads/writes the pointer.

Related