网站建设精英,小游戏不用实名认证的游戏,网站做照片,做网站第二年要续费吗目录
1.C 的存储类型
1.1.存储周期#xff08;Storage duration#xff09;
1.2.存储类型说明符#xff08;Storage class specifiers#xff09;
1.3.存储类型说明符与存储周期的关系
2.thread_local简介
3.thread_local 应用
3.1.thread_local 与全局变量
3.2.th…目录
1.C 的存储类型
1.1.存储周期Storage duration
1.2.存储类型说明符Storage class specifiers
1.3.存储类型说明符与存储周期的关系
2.thread_local简介
3.thread_local 应用
3.1.thread_local 与全局变量
3.2.thread_local 与 static变量
3.3.thread_local 与 成员变量
3.4.thread_local 与初始化
4.thread_local 的用处
5.性能考虑
6.替代方案
7.总结 1.C 的存储类型
1.1.存储周期Storage duration 存储周期表示一个变量的存储空间持续的时间它应该与对象的语义生命周期一致或至少不小于对象的语义生命周期。C 98从 C 继承了三种存储周期分别是静态存储周期static storage duration、自动存储周期automatic storage duration和动态存储周期dynamic storage durationC 11 又增加了一种线程存储周期thread storage duration。 存储周期只是一个概念是程序语义范畴内的东西但不是语法的范畴。这个概念在语法上的表示则由下一节介绍的存储类型说明符Storage class specifiers展示。
1.2.存储类型说明符Storage class specifiers 存储类型说明符Storage class specifiers也被称为存储类型它们是变量声明语法中类型说明符的一部分它们和变量名的范围一起控制变量的两个独立属性即存储周期storage duration和链接属性 linkage。C 98从 C 语言继承了 auto、register、static 和 extern 四种类型同时补充了一种 mutableC 11 针对线程存储周期又增加了一个线程本地存储的说明符 thread_local。关于这几个存储类型说明符的作用请参考下表
类型说明备注auto自动存储周期也是变量的默认存储类型由变量的域范围决定变量的存储周期比如局部变量的存储周期随着域的结束而结束而全局变量的存储周期则与程序的运行时间一致从 C11 开始显示使用 auto 存储类型会导致编译错误。比如 auto int i; 会导致编译错误register也是自动存储类型不过暗示编译器会择机将其放置在寄存器中以提高数据存取的效率在 C 17 被移除标准以后应避免使用这个存储类型static静态或线程存储周期采用内部链接对于不属于匿名名字空间anonymous namespace的静态类成员采用外部链接static 表示一个对象具有静态存储持续周期。它的生命周期是程序的整个执行过程其存储的值在程序启动之前只初始化一次extern静态或线程存储周期采用外部链接mutable严格来说这不是一种存储类型因为它既不影响变量的存储周期也不影响链接属性它只是表示一种可以“不动声色”地修改常量对象成员的机会。thread_local线程存储类型
1.3.存储类型说明符与存储周期的关系
C 中变量存储周期与变量类型说明符的关系如下表所示
存储周期变量类型与类型说明符自动存储周期显式使用 register 声明的变量或隐式声明为 static 或 extern 的作用域内部变量没有明确指定存储类型说明符的变量静态存储周期1、非 thread_local 声明的全局非局部变量2、非动态生成使用 new 创建的非局部变量3、用 static 声明的局部变量、全局变量和类成员变量动态存储周期1、使用 new 表达式创建非 placement_new并且使用 delete 销毁的对象2、使用其他动态分配函数和动态释放函数管理的对象存储位置线程存储周期使用 thread_local 声明的所有变量包括局部变量、全局变量和成员变量
2.thread_local简介 thread_local 是 C11 为线程安全引进的变量声明符。表示对象的生命周期属于线程存储期。 线程局部存储(Thread Local StorageTLS)是一种存储期(storage duration)对象的存储是在线程开始时分配线程结束时回收每个线程有该对象自己的实例如果类的成员函数内定义了 thread_local 变量则对于同一个线程内的该类的多个对象都会共享一个变量实例并且只会在第一次执行这个成员函数时初始化这个变量实例。 thread_local 一般用于需要保证线程安全的函数中。本质上就是线程域的全局静态变量。
3.thread_local 应用
3.1.thread_local 与全局变量 使用 thread_local 声明的变量会在每个线程中维护一个该变量的实例线程之间互不影响这里我们用一个普通的全局变量和一个 thread_local 类型的全局变量做对比说明一下这种存储类型的变量有什么性质。
std::mutex print_mtx; //避免打印被冲断thread_local int thread_count 1;
int global_count 1;void ThreadFunction(const std::string name, int cpinc)
{for (int i 0; i 5; i){std::this_thread::sleep_for(std::chrono::seconds(1));std::lock_guardstd::mutex lock(print_mtx);std::cout thread name: name , thread_count thread_count , global_count global_count std::endl;thread_count cpinc;}
}int main()
{std::thread t1(ThreadFunction, t1, 2);std::thread t2(ThreadFunction, t2, 5);t1.join();t2.join();
}
输出
thread name: t2, thread_count 1, global_count 1
thread name: t1, thread_count 1, global_count 2
thread name: t1, thread_count 3, global_count 3
thread name: t2, thread_count 6, global_count 4
thread name: t1, thread_count 5, global_count 5
thread name: t2, thread_count 11, global_count 6
thread name: t1, thread_count 7, global_count 7
thread name: t2, thread_count 16, global_count 8
thread name: t1, thread_count 9, global_count 9
thread name: t2, thread_count 21, global_count 10 可以看出来每个线程中的 thread_count 都是从 1 开始打印这印证了 thread_local 存储类型的变量会在线程开始时被初始化每个线程都初始化自己的那份实例。另外两个线程的打印数据也印证了 thread_count 的值在两个线程中互相不影响。作为对比的 global_count 是静态存储周期就没有这个特性两个线程互相产生了影响。
3.2.thread_local 与 static变量 thread_local 也可以用于局部变量的声明其作用域的约束与局部静态变量类似但是其存储与局部静态变量不一样首先是每个线程都有自己的变量实例其次是其生命周期与线程一致而局部静态变量的声明周期是直到程序结束。下面再用一个例子演示一下
void DoPrint(const std::string name, int cpinc)
{static int static_count 1;thread_local int local_count 1;std::cout thread name: name , local_count local_count , static_count static_count std::endl;local_count cpinc;
}void ThreadFunction(const std::string name, int cpinc)
{for (int i 0; i 5; i){std::this_thread::sleep_for(std::chrono::seconds(1));std::lock_guardstd::mutex lock(print_mtx);DoPrint(name, cpinc);}
}int main()
{std::thread t1(ThreadFunction, t1, 2);std::thread t2(ThreadFunction, t2, 5);t1.join();t2.join();
} 在上面的例子中static_count 和 local_count 变量的作用域都仅限于 DoPrint() 函数内部但是存储类型不一样local_count 在每个线程中的实例独立初始化独立变化线程之间没有影响而局部静态变量 static_count 则在两个线程之间互相影响。从结果打印的情况也印证了这一点
thread name: t1, local_count 1, static_count 1
thread name: t2, local_count 1, static_count 2
thread name: t1, local_count 3, static_count 3
thread name: t2, local_count 6, static_count 4
thread name: t2, local_count 11, static_count 5
thread name: t1, local_count 5, static_count 6
thread name: t1, local_count 7, static_count 7
thread name: t2, local_count 16, static_count 8
thread name: t1, local_count 9, static_count 9
thread name: t2, local_count 21, static_count 10
3.3.thread_local 与 成员变量 thread_local 可以用于类的成员变量但是只能用于静态成员变量。这很容易理解C 不能在对象只有一份拷贝的情况下弄出多个成员变量的实例但是静态成员就不一样了每个类的静态成员共享一个实例改成线程局部存储比较容易实现也容易理解。
class B {
public:B() {std::lock_guardstd::mutex lock(cout_mutex);std::cout create B std::endl;}~B() {}thread_local static int b_key;//thread_local int b_key;int b_value 24;static int b_static;
};thread_local int B::b_key 12;
int B::b_static 36;void thread_func(const std::string thread_name) {B b;for (int i 0; i 3; i) {b.b_key--;b.b_value--;b.b_static--; // not thread safestd::lock_guardstd::mutex lock(cout_mutex);std::cout thread[ thread_name ]: b_key: b.b_key , b_value: b.b_value , b_static: b.b_static std::endl;std::cout thread[ thread_name ]: B::key: B::b_key , b_value: b.b_value , b_static: B::b_static std::endl;return;
}
输出
create B
thread[t2]: b_key:11, b_value:23, b_static:35
thread[t2]: B::key:11, b_value:23, b_static: 35
thread[t2]: b_key:10, b_value:22, b_static:34
thread[t2]: B::key:10, b_value:22, b_static: 34
thread[t2]: b_key:9, b_value:21, b_static:33
thread[t2]: B::key:9, b_value:21, b_static: 33
create B
thread[t1]: b_key:11, b_value:23, b_static:32
thread[t1]: B::key:11, b_value:23, b_static: 32
thread[t1]: b_key:10, b_value:22, b_static:31
thread[t1]: B::key:10, b_value:22, b_static: 31
thread[t1]: b_key:9, b_value:21, b_static:30
thread[t1]: B::key:9, b_value:21, b_static: 30
b_key 是 thread_local虽然其也是 static 的但是每个线程中有一个每次线程中的所有调用共享这个变量。b_static 是真正的 static全局只有一个所有线程共享这个变量。
3.4.thread_local 与初始化
#include iostream
#include thread
#include mutex
std::mutex cout_mutex;//定义类
class A {
public:A() {std::lock_guardstd::mutex lock(cout_mutex);std::cout create A std::endl;}~A() {std::lock_guardstd::mutex lock(cout_mutex);std::cout destroy A std::endl;}int counter 0;int get_value() {return counter;}
};A* creatA() {return new A();
}void loopin_func(const std::string thread_name) {thread_local A* a creatA();std::lock_guardstd::mutex lock(cout_mutex);std::cout thread[ thread_name ]: a.counter: a-get_value() std::endl;return;
}void thread_func(const std::string thread_name) {for (int i 0; i 3; i) { loopin_func(thread_name);}return;
}int main() {std::thread t1(thread_func, t1);std::thread t2(thread_func, t2);t1.join();t2.join();return 0;
}
输出
create A
thread[t1]: a.counter:0
thread[t1]: a.counter:1
thread[t1]: a.counter:2
create A
thread[t2]: a.counter:0
thread[t2]: a.counter:1
thread[t2]: a.counter:2 虽然 createA() 看上去被调用了多次实际上只被调用了一次因为 thread_local 变量只会在每个线程最开始被调用的时候进行初始化并且只会被初始化一次。 举一反三如果不是初始化而是赋值则情况就不同了
void loopin_func(const std::string thread_name) {thread_local A* a;a creatA();std::lock_guardstd::mutex lock(cout_mutex);std::cout thread[ thread_name ]: a.counter: a-get_value() std::endl;return;
}
输出
create A
thread[t1]: a.counter:0
thread[t1]: a.counter:0
thread[t1]: a.counter:0
create A
thread[t2]: a.counter:0
thread[t2]: a.counter:0
thread[t2]: a.counter:0
很明显虽然只初始化一次但却可以被多次赋值因此 C 变量初始化是十分重要的。
4.thread_local 的用处 在 thread_local 提出之前你无法为一个线程定义自己的全局变量线程级别的全局变量只能将全局变量定义在父进程中由所有的线程不同种类的线程共享使用。但是当程序复杂到一定程度的时候线程之间的串扰就在所难免同时也增大了多线程编码的复杂度。前面的例子展示了 thread_local 的用法每个线程共享一个属于本线程的变量的实例相当于线程有了自己的全局变量。 另一个常用来解释 thread_local 的意义的例子就是随机数的生成。我们知道的随机数生成器都是伪随机数生成器其随机性取决于种子seed的变化。如果一个函数使用局部变量设置随机数发生器的种子那么它在每个使用这个函数的线程中都会被初始化由于使用了相同的种子每个线程将得到一样的随机数序列这就使得多线程也不那么随机了。如果使用 thread_local 类型的种子则每个线程维护自己的种子从而使得每个线程都能得到不同的随机数序列真正起到随机数的作用。 其他的例子就是线程不安全问题C 标准库的错误码 errno还有 strtok() 等函数就是线程不安全的例子。有了 thread_local 就可以用很小的改动解决这些函数的线程不安全问题。也不需要像有些编译器那样专门提供一套线程安全的标准库用过的人都知道很多函数的参数定义都是不兼容的对现有代码的改造成本非常高。
5.性能考虑
虽然 thread_local 变量提供了线程间的数据隔离但它们也可能带来一些性能开销。访问 thread_local 变量通常比访问常规的全局变量或栈变量要慢因为需要进行额外的 TLS 查找操作。因此在性能敏感的代码中应谨慎使用 thread_local。
6.替代方案
如果不需要真正的线程局部存储但只是想在线程之间传递数据可以考虑使用线程特定的数据Thread-Specific Data, TSD机制如 POSIX 的 pthread_key_create 和 pthread_setspecific 函数。对于跨平台的应用程序可以使用第三方库如 Boost.Thread来提供类似的功能。
7.总结 thread_local 关键字为 C 程序员提供了一种方便的方式来处理多线程环境中的线程特定数据。通过避免数据竞争和简化同步机制它可以帮助提高多线程程序的性能和可维护性。然而在使用时需要注意其性能开销和跨平台兼容性问题并根据具体场景选择合适的替代方案。