【问题标题】:Initialization of thread-local variables线程局部变量的初始化
【发布时间】:2018-09-23 23:32:31
【问题描述】:

我最近使用了一个对象,其目的是作为单例分配和释放内存。类似的东西

class MyValue
{
  // ...

  static Allocator& GetAllocator()
  {
    static Allocator allocator;
    return allocator;
  }

  // ...
};

后来我意识到Allocator 不是线程安全的:当多个线程同时使用同一个分配器时,偶尔会发生奇怪的事情,导致断言和分段错误。

解决方案:为不同的线程使用不同的分配器:

class MyValue
{
  // ...

  static Allocator& GetAllocator()
  {
    thread_local static Allocator allocator;
    return allocator;
  }

  // ...
};

太棒了!我的问题都没有了!只有一个问题: 是否会在每次创建线程时初始化我的分配器变量,即使大多数线程不会使用此变量?

分配器的初始化可能是繁重的操作,所以我希望它只在实际需要时才初始化,而不是在每个线程中。

我读到thread_local 变量由每个线程分配。这是否意味着它们也是构造的?这种分配(或构造)是针对每个创建的线程系统地发生还是仅针对使用它的线程?

我隐约记得在一门课程中听到关于线程和线程本地存储的大多数细节都依赖于平台。如果是这样的话,我对 Linux 和 FreeBSD 尤其感兴趣。


相关(有趣的读物,但我在那里找不到答案):

【问题讨论】:

标签: c++ multithreading c++11 thread-local-storage


【解决方案1】:

我可以给你一个解决方案来检查它是否在你每次调用GetAllocator() 时创建一个Allocator 类型的新对象。只需调用此方法至少 5 次并检查所有对象返回的地址。如果所有返回对象的地址不同,那么是的,它在每次调用中创建不同的对象,或者如果不是,它只是在每次调用 GetAllocator() 时返回相同对象的地址。

【讨论】:

  • 你提出的经验方法很清楚,但我想了解这个理论。我有足够的经验不再盲目相信简单实验的结果作为揭示复杂系统背后真相的一种方式......
【解决方案2】:

[basic.stc.thread] 状态

  1. 使用thread_local 关键字声明的所有变量都有线程存储持续时间。这些实体的存储将持续到创建它们的线程的持续时间。每个线程都有一个不同的对象或引用,使用声明的名称是指与当前线程关联的实体。

  2. 具有线程存储持续时间的变量应在其首次使用 odr (6.2) 之前进行初始化,如果已构建,则应在线程退出时销毁。

因此,您将在每个线程中获得对象的存储空间。我们也有 [stmt.dcl]/4 声明

在控制第一次通过其声明时执行具有静态存储持续时间 (6.7.1) 或线程存储持续时间 (6.7.2) 的块范围变量的动态初始化;这样的变量在其初始化完成时被认为已初始化。

所以,如果我们到达声明,那么对象将被初始化,如果它有构造函数,它将被调用。

如果我们将所有这些放在一起,您将拥有与实际到达的线程数相等的构造函数和析构函数调用数

thread_local static Allocator allocator;

【讨论】:

  • 你的意思是线程局部变量,即使没有在线程中使用,存储也会一直分配,但不会被初始化(构造函数)?
  • @BaiyanHuang 根据我对一切运作方式的理解,是的。我在标准中找不到任何内容来说明是否必须如此,但我相信所有实现都会在堆栈上为所有局部变量分配空间,无论它们是否已初始化。
  • 是的,标准倾向于说这留给实现,我写了一个例子,我可以看到构造函数只在线程本地被使用的线程中调用,但不确定存储是否预先- 为所有线程分配或仅在调用构造函数之前仅在那些引用它的线程中分配
猜你喜欢
  • 2017-05-14
  • 2019-01-19
  • 1970-01-01
  • 2021-03-17
  • 2016-09-07
  • 2012-10-03
  • 2013-05-20
  • 2012-08-18
  • 2016-01-14
相关资源
最近更新 更多