是否允许泄漏线程局部变量的指针?

Is it allowed to leak pointers of thread local variable?

我目前正在尝试更好地理解 C++ 中的线程本地存储。为此我写了下面的例子:

#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;
}

可以看出,thread_local变量的地址泄漏到程序中,线程局部变量无意中 由另一个线程更改。所有对全局状态的访问都受到 std::mutex 的保护,因此不应该存在竞争。实际上,我看到了以下预期输出:

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
...

我的问题是:关于从另一个线程访问线程局部变量,标准是怎么说的?线程局部变量仅仅是一个指向保证每个线程唯一的内存的指针,还是标准强制要求“线程局部”访问?

它与任何其他本地对象完全相同。所有线程共享相同的内存视图,函数或线程可以将指向本地对象的指针传递给另一个线程或函数(如果需要)。如果您这样做,您有责任确保不存在竞争条件并且不会在其生命周期之外访问对象。

我不同意此处“无意中”更改的任何内容。线程具有相同的内存视图,因此在一个线程中指向对象的指针将在该对象的生命周期内指向另一个线程中的同一个对象。

情况与线程堆栈上的对象相同。它们是本地的,因为每个线程都有自己的堆栈。但是一个线程向另一个线程提供指向其堆栈上的对象的指针或引用是完全合法的。线程安全和生命周期问题一如既往地由您负责。

不需要线程本地访问。但是,您需要注意示例代码的一个微妙之处:

线程本地存储中的数据在拥有线程退出时被删除。这意味着发布的代码不能保证有效。如果 show_index 线程在 change_index 线程之前退出,则 change_index 中的数据访问可能无效,并且该指针可能指向垃圾或其他线程 TLS。因为线程涉及无限循环,所以它们永远不会自行终止。但是,当您终止程序时,show_index 线程可能会在 change_index 线程 reads/writes 指针之前被终止。