C++之thread_local
文章目录
前言
thread_local
是C++11引入的关键字,该关键字会影响一个变量的存储周期。本文简单介绍C++的存储周期以及该关键字带来的影响。
1.C++的存储周期
C++中的存储周期是指变量或者对象存在的时期,也就是说一个对象或者变量从创建到销毁的时间跨度。存储周期与作用域有关,但是二者不等同。对于一个作用域来说,主要描述变量在何处可以被访问,存储周期则描述了变量何时存在,C++的存储周期分为四类
1.1静态存储周期(Static storage duration)
具有静态存储周期的对象在其程序的整个运行期间都会存在,知道程序退出位置。静态存储周期的对象通常在程序启动时创建并在结束时销毁,这类对象包括全局变量,静态局部变量以及带有static
存储类的对象。
该类对象的特性如下:
- 程序运行期间始终存在
- 通常在全局数据区分配内存
- 静态局部变量在函数每次调用时,都会保持上次调用的状态
- 除非作用域限制,全局变量在整个程序中可被访问
1.2 动态存储周期(Dynamic storage duration)
这类对象或者变量是在程序运行时通过动态内存分配函数创建的,例如使用new
或者malloc
。这些对象的声明周期由程序员通过嗲用相应的释放内存函数来控制,如delete
或free
。
该类对象特性如下:
- 生命周期由程序员控制
- 通常在堆区分配内存
- 需要手动管理内存分配释放
1.3 自动存储周期(Automatic storage duration)
具有自动存储周期的对象在其作用域内存在,一旦离开作用域就会被自动销毁,这类对象通常是在函数体或者复合语句中声明的局部变量。
这类对象特性如下:
- 在进入作用域时创建,离开作用域时被销毁
- 通常在栈区分配内存
- 大部分情况下生命周期和所在函数的声明周期保持一致
1.4 线程存储周期(thread local storage duration)
在C++11中引入,允许多线程程序中的每个线程拥有自己单独的变量实例,这些变量的声明周期通常和所属的线程的声明周期一样长。
这类对象特性如下:
- 从初始化开始到线程终止结束
- 在线程范围级别具有可见性
2.thread_local
有且只有thread_local
关键字修饰的变量会具有线程存储周期。如前文所述,在线程开始的时候被生成,在线程结束的时候被销毁。
该类对象具有如下特性:
- 线程安全性:每个线程拥有自己独立的副本,因此对
thread_local
关键字修饰的变量进行操作不需要加锁 - 声明周期:线程局部变量的生命周期与其所属的线程的生命周期相同。当线程创建时,变量的副本被创建;当线程终止时,副本被销毁。
#include <iostream>
#include <thread>
thread_local int thread_count = 0;
void increment_and_print() {
++thread_count;
std::cout << "Thread " << std::this_thread::get_id() << " count: " << thread_count << std::endl;
}
int main() {
std::thread t1(increment_and_print);
std::thread t2(increment_and_print);
t1.join();
t2.join();
return 0;
}
上面的是一个示例的C++程序,定义了一个函数increment_and_print
对线程变量进行自增操作,分别创建两个线程调用该函数,执行结果如下所示:
可以看到对于不同的线程来说,分别拥有一个变量副本,在各自的线程中分别实现了+1的自增操作。
#include <iostream>
#include <thread>
class share
{
public:
void addself()
{
thread_local int thread_count = 0;
thread_count++;
std::cout << "count: " << thread_count << std::endl;
}
};
void increment_and_print() {
share s1;
share s2;
s1.addself();
s2.addself();
}
int main() {
std::thread t1(increment_and_print);
t1.join();
return 0;
}
这里还有一段比较有意思的程序,该程序声明了一个类,并且该类的成员函数里存在一个thread_local
关键字修饰的变量,并对其完成了初始化为0和自增的操作。
在下面定义的线程函数里,实例化了两个对象,分别调用各自的自增函数,输出结果如下:
结果分别是1和2,并非我们设想的1和1,这是因为类的成员函数内定义的 thread_local 变量,同一个线程内的该类的多个对象都会共享一个变量实例,并且只会在第一次执行这个成员函数时初始化这个变量实例,类似于类的静态成员变量。