C++ thread_local

2024 年 8 月 29 日 | 5 分钟阅读

在本文中,您将学习 C++ 中的 thread_local 及其语法和示例。

什么是 thread_local?

thread_local 关键字允许您声明具有线程局部存储持续时间的变量。这意味着每个访问该变量的线程都将获得该变量的副本。

语法

它具有以下语法:

在这里,每个线程都将拥有变量 var 的副本。如果一个线程修改了 var,它不会影响其他线程中 var 的值。

关于 thread_local 变量的一些关键点

  • 它们在线程访问时初始化,而不是在程序启动时初始化。
  • 它们将在线程退出时销毁。
  • 静态线程局部变量的行为类似于所有线程都可以访问的标准静态变量,但每个线程都获得一个单独的副本。
  • thread_local 可以应用于静态和非静态变量。
  • thread_local 变量可以具有类等复杂类型。各个副本使用默认构造函数初始化。

当您希望变量对每个线程都是私有的而不是在所有线程之间共享时,thread_local 关键字非常有用。它避免了在线程之间显式传递变量,并防止了围绕访问的竞争条件。

thread_local 的常见用例包括每个线程缓存、每个线程随机数生成器、日志记录、特定于单个线程的指标收集器等。

线程局部存储的属性是什么?

以下是 C++ 中线程局部存储 (TLS) 的一些主要属性和行为

  • 生命周期 - 它在线程首次访问时开始,并在该线程终止时结束。
  • 可见性 - 每个线程都获得该变量的单独副本。
  • 范围
  • 命名空间范围 - 声明后可在整个代码中访问。
  • 类范围 - 可供所有成员函数访问。
  • 函数/块范围 - 可在该代码块内访问。

Thread_local 变量的生命周期与它们首次访问时初始化的线程相关联。它们的可见性是线程特定的,每个线程都获得自己的副本。范围取决于上下文,它可能仅限于一个块,在类内共享,或者在声明后全局可用。

C++ thread_local 示例

示例 1

让我们举一个例子来说明 C++ 中 线程局部存储 的使用。

输出

Thread ID: 12345 - Thread Local Variable: 1
Thread ID: 67890 - Thread Local Variable: 1
Main Thread ID: 98765 - Thread Local Variable: 0

示例 2

让我们看一个 C++ 程序,它展示了如何使用线程局部存储。

输出

Thread ID: 12345 - Thread Local Variable: 1
Thread ID: 67890 - Thread Local Variable: 1
Main Thread ID: 98765 - Thread Local Variable: 0

静态线程局部存储

thread_local 关键字允许声明变量,以便每个访问该变量的线程都获得其副本。它允许多个线程使用相同的变量,而无需在线程之间显式传递数据。它还通过锁定共享数据来避免竞争条件和复杂性。每个线程副本在首次访问时分配,并在线程终止时释放。对于静态线程局部变量,这种每个线程分配在首次访问时只发生一次,存储持续到程序的生命周期。但是,与自动 thread_local 变量一样,每个线程仍然获得其私有副本。它使 thread_local 对封装线程特定数据(如缓存、日志记录器、用户上下文等)非常有用。但是,如果通过指针而不是直接访问 thread_local 变量,则需要小心。指针需要根据每个线程的生命周期进行适当管理。线程局部存储的隔离和快速访问是以每个线程拥有单独副本而增加的内存使用为代价的。

静态局部存储示例

示例 1

让我们举一个例子来说明 C++ 中 静态线程局部存储 的使用。

输出

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

C++ thread_local 的规则和限制

以下是使用 C++ 中 thread_local 时需要记住的一些基本规则和限制

  1. 销毁顺序 - C++ 标准中未指定线程退出时 thread_local 对象的销毁顺序。不要依赖于线程局部事物的特定销毁顺序。
  2. 动态初始化 - thread_local 变量在线程首次访问时动态初始化。它们的构造函数不能依赖于其他 thread_local 变量已经初始化。
  3. 性能开销 - 每个线程拥有变量副本会产生内存开销,该开销会随着线程数量的增加而增加。缓存性能也可能受到负面影响。
  4. 默认私有 - thread_local 默认情况下为每个线程的每个变量引入一个私有范围。数据无法在线程之间直接共享,而无需显式传递。
  5. 平台支持 - 某些编译器可能不完全支持 thread_local 变量,尤其是某些旧编译器。检查您的目标平台的兼容性。
  6. 范围限制 - thread_local 仅适用于在命名空间、类和块范围中声明的变量。您不能将其用于函数参数或方法返回类型。
  7. 链接限制 - thread_local 不能用于使用 extern 关键字声明的变量。它与外部链接不兼容。
  8. 类型限制 - thread_local 不能应用于 C++ 中的引用类型。它仅适用于对象类型和原始类型。

总之,thread_local 非常有用,但在使用时必须考虑销毁顺序、初始化、开销和范围支持等一些限制。