概念

首先thread_local是一个关键词,thread_local是C++ 11新引入的一种存储期指定符。它会影响变量的存储周期(Storage duration),与它同是存储期指定符的还有以下几个:

关键字 说明 备注
auto 自动存储期 c++11前, “auto int x; ” 在c++11起错误
register 自动存储期。指示编译器将此对象置于处理器的寄存器中。 c++17弃用
static 静态或者线程存储期的内部链接
extern 静态或者线程存储期的外部链接
thread_local 线程存储期 c++11起
mutable 不影响存储期或链接

thread_local指示对象拥有线程存储期。也就是对象的存储在线程开始时分配,而在线程结束时解分配。每个线程拥有其自身的对象实例。唯有声明为 thread_local 的对象拥有此存储期。 thread_local 能与 static 或 extern 结合一同出现,以调整链接(分别指定内部或外部链接),详细的可以查阅:存储类说明符 - cppreference.com。

thread_local 关键词只对声明于命名空间作用域的对象、声明于块作用域的对象及静态数据成员允许。举例如下:

thread_local int x;  // 1 A thread-local variable at namespace scope
class X
{static thread_local std::string s; // 2 A thread-local static class data member
};
static thread_local std::string X::s;  //The definition of X::s is requiredvoid foo()
{thread_local std::vector<int> v;  // 3 A thread-local local variable
}

下面我们来具体学习一下thread_local的这几种应用场景。

全局变量

#include <iostream>
#include <thread>
#include <mutex>std::mutex cout_mutex;    // 用于多线程打印
thread_local int x = 1;void func(const std::string& thread_name) {for (int i = 0; i < 3; ++i) {x++;std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "thread[" << thread_name << "]: x = " << x << std::endl;}
}int main() {std::thread t1(func, "t1");std::thread t2(func, "t2");t1.join();t2.join();return 0;
}

输出:

thread[t1]: x = 2
thread[t1]: x = 3
thread[t1]: x = 4
thread[t2]: x = 2
thread[t2]: x = 3
thread[t2]: x = 4

从上输出也确实能看出,每个线程都有自己单独的x副本,互不干预。

局部变量

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;    //方便多线程打印void func(const std::string& thread_name) {for (int i = 0; i < 3; ++i) {thread_local int x = 1;x++;std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "thread[" << thread_name << "]: x = " << x << std::endl;}
}int main() {std::thread t1(func, "t1");std::thread t2(func, "t2");t1.join();t2.join();return 0;
}

输出:

thread[t1]: x = 2
thread[t1]: x = 3
thread[t1]: x = 4
thread[t2]: x = 2
thread[t2]: x = 3
thread[t2]: x = 4

thread_local的局部变量没有因为for循环作用域而重新赋值。这是因为线程存储期的变量都是和线程绑定的,所以只有第一次声明时被赋值。可以理解为线程专用的static变量。不过变量的作用域依然是在本身的作用域内。比如:在for循环外使用x就会编译时错误。

类对象

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;class A {
public:A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "initialize A" << std::endl;}~A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "destroy A" << std::endl;}int counter = 0;int get_value() {return counter++;}
};void func(const std::string& thread_name) {for (int i = 0; i < 3; ++i) {thread_local A* a = new A();std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "thread[" << thread_name<< "]: a.counter:" << a->get_value() << std::endl;}
}int main() {std::thread t1(func, "t1");std::thread t2(func, "t2");t1.join();t2.join();return 0;
}

输出:

initialize A
thread[t1]: a.counter:0
thread[t1]: a.counter:1
thread[t1]: a.counter:2
initialize A
thread[t2]: a.counter:0
thread[t2]: a.counter:1
thread[t2]: a.counter:2

可以看出虽然在循环中创建了A的实例a,但是并没有因为循环创建了多个。这个与局部变量的情况相同,创建的实例相对于thread是static的。

但是如果没有在声明时进行赋值,就不一样了。如:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;class A {
public:A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "initialize A" << std::endl;}~A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "destroy A" << std::endl;}int counter = 0;int get_value() {return counter++;}
};void func(const std::string& thread_name) {for (int i = 0; i < 3; i++) {thread_local A* a;a = new A();std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "thread[" << thread_name << "]: a.counter:" << a->get_value() << std::endl;}return;
}int main() {std::thread t1(func, "t1");std::thread t2(func, "t2");t1.join();t2.join();return 0;
}

输出:

initialize A
thread[t1]: a.counter:0
initialize A
thread[t1]: a.counter:0
initialize A
thread[t1]: a.counter:0
initialize A
thread[t2]: a.counter:0
initialize A
thread[t2]: a.counter:0
initialize A
thread[t2]: a.counter:0

很好理解,在循环执行时,每次都创建了一个A实例并对a进行赋值。所有一般情况要求我们:thread_local对象声明时赋值

类成员变量

thread_local作为类成员变量时必须是static的,修改代码:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;class A {
public:A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "initialize A" << std::endl;}~A() {std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "destroy A" << std::endl;}thread_local static int key_;int value_ = 24;static int static_;
};
int A::static_ = 36;
thread_local int A::key_ = 12;void func(const std::string& thread_name) {A aa;for (int i = 0; i < 3; ++i) {aa.key_--;aa.value_--;aa.static_--;std::lock_guard<std::mutex> lock(cout_mutex);std::cout << "thread[" << thread_name << "]: key_:" << aa.key_<< ", value_:" << aa.value_ << ", static_:" << aa.static_ << std::endl;std::cout << "thread[" << thread_name << "]: A::key_:" << A::key_<< ", value_:" << aa.value_ << ", static_: " << A::static_ << std::endl;}return;
}int main() {std::thread t1(func, "t1");std::thread t2(func, "t2");t1.join();t2.join();return 0;
}

输出:

initialize A
thread[t1]: key_:11, value_:23, static_:35
thread[t1]: A::key_:11, value_:23, static_: 35
thread[t1]: key_:10, value_:22, static_:34
thread[t1]: A::key_:10, value_:22, static_: 34
thread[t1]: key_:9, value_:21, static_:33
thread[t1]: A::key_:9, value_:21, static_: 33
destroy A
initialize A
thread[t2]: key_:11, value_:23, static_:32
thread[t2]: A::key_:11, value_:23, static_: 32
thread[t2]: key_:10, value_:22, static_:31
thread[t2]: A::key_:10, value_:22, static_: 31
thread[t2]: key_:9, value_:21, static_:30
thread[t2]: A::key_:9, value_:21, static_: 30
destroy A

从上面例子可以看出thread_local作为类成员时也是对于每个thread分别分配了一个。而static则是全局一个。

其他

本质上thread_local修饰后仍然是一个变量,我们依旧能够使用取地址操作者通过引用的方法传递给其他线程对其进行修改:

#include <iostream>
#include <thread>thread_local int i=0;void func(int* p){*p = 42;
}int main(){i = 9;std::thread t(func, &i);t.join();std::cout << i << std::endl;
}

程序将输出42

另外,thread_local 变量在第一次使用时初始化,如果变量(类)没有被使用。此变量(类)将不会被初始化:

#include <iostream>
#include <thread>struct A {A() {std::cout<< "initialized A" << std::endl;}~A() {std::cout << "deleted A" << std::endl;}int i;
};thread_local my_class ss;void do_nothing() {
}int main(){std::thread t1(do_nothing);t1.join();
}

总结

thread-local storage 和 static(或者说global) 存储很类似,每一个线程都将拥有一份这个数据的拷贝,thread_local对象的生命周期从线程开始时开始(对于全局变量),或者首先分配空间。当线程退出的时候对象析构;

一般在声明时赋值,在本thread中只执行一次。当用于类成员变量时,必须是static的。

参考:

C++11 thread_local用法 - 知乎 (zhihu.com)

存储类说明符 - cppreference.com

C++多线程:thread_local相关推荐

  1. C++11 (多线程)并发编程总结

    | 线程 std::thread 创建std::thread,一般会绑定一个底层的线程.若该thread还绑定好函数对象,则即刻将该函数运行于thread的底层线程. 线程相关的很多默认是move语义 ...

  2. C++多线程快速入门(二)共享数据同步以及数据竞争

    目录 std::unique_lock类模板 仅调用一次 线程局部存储 原子变量 往期内容回顾 std::unique_lock类模板 互斥锁保证了线程间的同步,却将并行操作变成了串行操作,对性能有较 ...

  3. Feign接口 多线程问题

    Spring Cloud Feign传输Header,并保证多线程情况下也适用 一.现象 微服务在生产中,常遇到需要把 header 传递到下一子服务的情况(如服务A访问服务B的接口,需要传递head ...

  4. [C++11 std::thread] 使用C++11 编写 Linux 多线程程序

    From: http://www.ibm.com/developerworks/cn/linux/1412_zhupx_thread/index.html 本文讲述了如何使用 C++11 编写 Lin ...

  5. Linux多线程实践(10) --使用 C++11 编写 Linux 多线程程序

    在这个多核时代,如何充分利用每个 CPU 内核是一个绕不开的话题,从需要为成千上万的用户同时提供服务的服务端应用程序,到需要同时打开十几个页面,每个页面都有几十上百个链接的 web 浏览器应用程序,从 ...

  6. C/C++开发,无可避免的多线程(篇二).thread与其支持库

    目录 一.原子类型与原子操作 1.1 原子类型与操作介绍 1.2 c++11的原子类型 1.3 原子操作功能与应用 1.4 原子布尔类型 1.5 原子操作-内存同步顺序 二. 原子操作库 2.1 原子 ...

  7. C/C++编译器并行优化技术:并行优化针对多核处理器和多线程环境进行优化,以提高程序的并行度

    目录标题 引言 数据并行:将数据集分割成多个子集,分配给多个线程或处理器并行处理. 延迟执行与乱序执行:对指令的执行顺序进行调整,提高指令流水线的利用率和性能. 延迟执行 乱序执行 任务并行:将程序分 ...

  8. C++11中thread_local的使用

    C++11中的thread_local是C++存储期的一种,属于线程存储期.存储期定义C++程序中变量/函数的范围(可见性)和生命周期.C++程序中可用的存储期包括auto.register.stat ...

  9. C语言多线程并行的利器之线程变量

    线程变量 在C语言中,如果函数使用的变量或内存均为栈变量,则函数可以充分保证线程的并行执行,但栈变量的生命周期却很短,无法广泛适用于并行编程的指导建议! 如果你想每个线程使用的变量或存储区域,在内存分 ...

最新文章

  1. iOS 之 事件响应者链
  2. 二分查找之搜索插入位置
  3. Ubuntu 下搭建 NFS 服务
  4. anaconda的执行路径
  5. 3. 中间件安全基础(三)
  6. 想知道3D游戏建模师每天都在做什么吗?3D游戏建模的那些事
  7. OBJECTPROPERTY OBJECT_ID TEXTPTR【转载】
  8. 【bzoj4518】[Sdoi2016]征途 斜率优化dp
  9. 低照度图像修复方法总结
  10. 金融期货具体的开户条件
  11. wps怎么写分段函数_wps宏怎么写
  12. 国开网电大 动物常见病防治 形考任务1-5
  13. 电脑重装系统不小心格式化了恢复文件教程
  14. 使用windbg调试器定位程序的内存泄漏
  15. C#调用FluentFTP将文件批量上传到ftp服务器
  16. 智能手机基于众包的室内定位
  17. 案例:使用pre_trained模型进行VGG
  18. hbase2.1.6 mr
  19. 计算机志愿者维修服务队,信息技术志愿者服务队
  20. Mac有滚动截图工具吗?----解救 MAC 使用者们的高效截图工具- Xnip

热门文章

  1. Linux提高:僵尸进程
  2. linux 僵尸进程处理
  3. ZVS空载电流大及波形畸变问题
  4. 91文件夹操作——创建和删除多级目录
  5. Android监听应用切换到后台和前台
  6. dann的alpha torch_pytorch BiLSTM+CRF代码详解
  7. discuz3.4安装php,Discuz!X3.4论坛源码下载 及 全新安装教程
  8. function介绍
  9. 计算机二级投影运算怎么看,二级计算机中交、并、除、自然连接、投影、选择和笛卡尔积是怎么计算的?...
  10. SecureCRT for Linux