gcc版本为(gcc version 7.3.0 (Debian 7.3.0-19))

引言

两个关键字都是关于线程存储的,不过一个是C语言的,一个是C++11的特性。它们之间有什么区别呢?因为在CSDN没有找到解答,遂在解答中对过程进行记录,以帮助有同样疑惑的同学。

过程

__thread is supported on GNU, clang and more. It was available before thread_local… they are not equivalent and both are supported. the difference is that thread_local uses lazy initialization to initialize the variable in only threads that access it. __thread does not initialize at all and you must manually initialize it per thread. thread_local thus has an overhead per access and __thread does not. Apple’s compilers disable thread_local and not thread because of this inefficiency, Although __thread is not available on all compilers, __thread is available with GNU tools

__thread被GNU支持,clang等支持.它在thread_local之前是很有用的…它们之间并不相等,而且一般都被支持.它们之间的不同就是thread_local在访问变量的线程中使用延迟初始化来初始话变量.而__thread并不初始化,您必须手动初始化.所以每一次thread_local都是有开销的,而__thread没有.因为这种低效率,Apple的编译器禁用thread_local而不禁用thread,尽管__thread不是在所有编译器上都可用,但_其在GNU工具中可用.(thread_local是C++11的特性)

我们可以看到区别就是延迟初始化带来的效率上的区别,这里的的开销指的是什么呢?开销就是 thread_local 变量的每次使用都将成为一个函数调用。这让人感到不可思议。

这里的过程可以查看参考[4],因为C++生成的汇编代码实在是过于复杂,我在我机子上生成汇编以后看了半天没看出来,所以大家直接查看参考即可,那篇文章还是比较详细的。

看到有些地方说__thread不支持class相关的构造,析构,我们写一个代码看看:

#include <bits/stdc++.h>
#include <pthread.h>
using namespace std;class local_{public:local_(){cout << ::pthread_self() << endl;cout << "hello world\n";}void show(){cout << "hello world1\n";}~local_(){cout << "hollo world2\n";}
};thread_local local_ Temp;
//__thread local_ TT;void test(){std::cout << "test : " << ::pthread_self()<< endl; Temp.show();
}int main(){std::cout << "main : " << ::pthread_self()<< endl; auto T = std::thread(&test);Temp.show();T.join();cout << "end\n";return 0;
}

输出:

main : 140093008275264
140093008275264
hello world
hello world1
test : 140093008270912
140093008270912
hello world
hello world1
hollo world2
end
hollo world2

没有什么问题,线程局部变量和全局的Temp都经历了一次构造和析构。

但是如果我们修改test函数如下,也就是不使用子线程内的Temp

void test(){std::cout << "test : " << ::pthread_self()<< endl;
}

此时输出为:

main : 139735341807424
139735341807424
test : hello world
139735341803072
hello world1
end
hollo world2

我们可以看到子线程内的Temp没有被初始化,这难道就是延迟初始化?我们在把main函数中的Temp去掉,并重新使用子线程内的Temp,结果如下:

main : 139983638738752
test : 139983638734400
139983638734400
hello world
hello world1
hollo world2
end

主线程内的Temp没有被创建。

最后把thread_loacl声明为static看一看:

static thread_local local_ Temp;void test(){std::cout << "test : " << ::pthread_self()<< endl; Temp.show();
}
main : 140635869771584
test : 140635869767232
140635869767232
hello world
hello world1
hollo world2
end

可看到仍然进行了初始化。

那么如果使用__thread的话呢?我们注释掉thread_local local_ Temp这一句,使用__thread,得到这样的结果:

non-local variable ‘Temp’ declared ‘__thread’ needs dynamic initialization

我们可以在[6]中找到如下资料:

  • The __thread specifier may be applied to any global, file-scoped static, function-scoped static, or static data member of a class. It may not be applied to block-scoped automatic or non-static data member.
  • __thread可以被用于类的全局静态、文件作用域静态、函数作用域静态或静态数据成员。它可能不适用于块作用域自动或非静态数据成员。

在[8]中可以看到如下描述,我没找到他这段话的出处,所以只能抱着半信半疑的态度来看:

只能修饰POD类型(类似整型指针的标量,不带自定义的构造、拷贝、赋值、析构的类型,二进制内容可以任意复制memset,memcpy,且内容可以复原),

需要动态初始化,__thread是不支持非函数本地变量的。那么如果让它跑起来呢,我们执行动态初始化,且修改类型为指针就可以,我们把代码修改成如下:

static __thread local_* Temp;
//__thread local_ TT;void test(){std::cout << "test : " << ::pthread_self()<< endl; static __thread local_ T;Temp = new local_();Temp->show();delete Temp;
}

输出为:

main : 140161363343168
test : 140161363338816
140161363338816
hello world
140161363338816
hello world
hello world1
hollo world2
hollo world2
end

我们可以看到局部静态变量和全局静态成员都初始化成功。
从前面的报错可以看出:non-local variable声明为__thread需要动态初始化,那local就不需要喽。

non-local variable ‘Temp’ declared ‘__thread’ needs dynamic initialization.

test改成如下试一试:

void test(){std::cout << "test : " << ::pthread_self()<< endl; __thread local_ T;Temp = new local_();Temp->show();delete Temp;
}

也可以跑通:

main : 139799995848512
test : 139799995844160
139799995844160
hello world
139799995844160
hello world
hello world1
hollo world2
hollo world2
end

__thread被声明为局部变量的时候仍然会发生构造和析构,所以传言被打破了。

在[9]中我们可以看到以下文字;

  • The thread_local keyword is only allowed for objects declared at namespace scope, objects declared at block scope, and static data members. It indicates that the object has thread storage duration. It can be combined with static or extern to specify internal or external linkage (except for static data members which always have external linkage), respectively, but that additional static doesn’t affect the storage duration.
  • 仅在命名空间范围内声明的对象,在块范围内声明的对象和静态数据成员才允许使用thread_local关键字。 它指示对象具有线程存储持续时间。 可以将其与static或extern组合以分别指定内部或外部链接(始终具有外部链接的静态数据成员除外),但是附加的static不会影响存储时间。

其中的粒度是对象,可以看到明确的说明了:指示对象具有线程存储持续时间。

  • thread storage duration. The storage for the object is allocated when the thread begins and deallocated when the thread ends. Each thread has its own instance of the object. Only objects declared thread_local have this storage duration. thread_local can appear together with static or extern to adjust linkage. See Non-local variables and Static local variables for details on initialization of objects with this storage duration.
  • 线程存储持续时间。 对象的存储在线程开始时分配,并在线程结束时释放。 每个线程都有其自己的对象实例。 只有声明为thread_local的对象才具有此存储时间。 thread_local可以与static或extern一起出现以调整链接。 有关使用此存储持续时间初始化对象的详细信息,请参见非局部变量和静态局部变量。

官网中并没有提到延迟初始化,但是在[10]中提到了如下文字:

  • If relevant, constant initialization is applied.Otherwise, non-local static and thread-local variables are zero-initialized.
  • 如果相关,则应用常量初始化。否则non-local static 和 thread-local变量会零初始化。

结论

通过以上测试与查阅资料,得出以下结论。

  1. 执行阶段的效率问题。__thread不会发生函数调用,而thread_local对变量的一次访问就是一次函数调用。(非官方)
  2. thread_local可以延迟初始化。(没使用就不声明可能是被编译器优化掉了,不过我开的是 -O0)。(非官方)
  3. __thread在某些平台上支持,比如GNU,clang等,但是有些平台不支持。thread_local作为C++11的特性,基本都被支持
  4. __thread在实现非本地变量的时候(非POD类型)需要动态初始化。而thread_local在任何情况(命名空间范围内声明的对象,在块范围内声明的对象和静态数据成员)都可以静态初始化(class存在有效的默认构造函数),不需要我们动态初始化。
  5. 两个关键字都支持类的构造与析构

作者水平有限,有问题的地方还请大家给出宝贵的建议。

参考:

  1. 博文《linux编程 - C/C++每线程(thread-local)变量的使用》
  2. 问答《Using __thread in c++0x :stackoverflow》
  3. 文档《Thread-Local Storage :文档》
  4. 博文《C ++ 11:GCC 4.8 thread_local 性能惩罚?(C++11: GCC 4.8 thread_local Performance Penalty?)》
  5. 博文《c++ 线程局部变量thread_local》
  6. https://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Thread_002dLocal.html
  7. 《关于__thread和__type__(var)两个gcc扩展》
  8. 《__thread关键字》
  9. https://en.cppreference.com/w/cpp/language/storage_duration
  10. https://en.cppreference.com/w/cpp/language/initialization#Non-local_variables

thread_local与__thread的区别相关推荐

  1. 跟着Rocskdb 学 存储引擎:读写链路的代码极致优化

    文章目录 1. 读链路 1.1 FileIndexer 1.1.1 LevelDB sst查找实现 1.1.2 Rocksdb FileIndexer实现 1.2 PinnableSlice 减少内存 ...

  2. RPC 笔记(01)— RPC概念、调用流程、RPC 与 Restful API 区别

    1. 基本概念 PRC 远程过程调用 Remote Procedure Call,其就是一个节点请求另外一个节点提供的服务.当两个物理分离的子系统需要建立逻辑上的关联时,RPC 是牵线搭桥的常见技术手 ...

  3. C++ 笔记(28)— C++ 中 NULL和 nullptr 的区别

    最近看公司代码的时候发现在判断指针是否为空的时候,有的时候用的是 NULL, 有的时候用的是 nullptr 感觉很奇怪,好奇心驱使我查了下两者的区别,发现还是有很多细节需要学习的. 1. NULL ...

  4. gcc 和 g++ 的联系和区别,使用 gcc 编译 c++

    GCC 编译器已经为我们提供了调用它的接口,对于 C 语言或者 C++ 程序,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器. 实际使用中我们更习惯使用 gcc 指令编译 C 语言程序 ...

  5. Python2 与 Python3 区别

    Python2.x 与 Python3.x 区别 1. print 函数 Python2 中 print 是语句(statement),Python3 中 print 则变成了函数.在 Python3 ...

  6. Docker 入门系列(1)- 初识容器,镜像、容器、仓库的区别

    Docker 简介 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发 ...

  7. HTTP 协议入门 — (TCP/IP协议族、通信传输流、URI 与 URL 的区别、Cookie 状态管理、HTTP 支持的方法、状态码类别、HTTP 首部字段)

    TCP/IP协议族 在介绍 HTTP 协议之前,我们先对 TCP/IP 协议族有个大概的了解,TCP/IP 协议从上到下主要分为应用层.传输层.网络层和数据链路层,各层的主要功能如下表所示: 协议层 ...

  8. python二进制打开(rb)和文本格式打开(r)什么区别?

    使用 open() 函数以文本格式打开文件和以二进制格式打开文件,唯一的区别是对文件中换行符的处理不同. 在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行 ...

  9. python中__dict__与dir()区别

    前言 Python下一切皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案. __dict__与dir()的区别: dir()是一个函数,返回的是list: _ ...

最新文章

  1. 业余快速学习虚幻引擎教程
  2. linux操作系统重启后 解决nginx的pid消失问题
  3. 选项卡 都是显示在页面底部
  4. redis压力测试详解
  5. 优秀项目经理必备的8个要素
  6. awk文本工具按列计算和
  7. java 从未导入_Java 8的10个您从未听说过的功能
  8. Oracle EXP/IMP参数详解
  9. Activity 模版样式简介
  10. wav格式的音频文件 16位转化成8位的
  11. linux qt调用摄像头,Qt5下实现摄像头预览及捕获图像方法实例
  12. 在vue项目中使用 JSON 编辑器: vue-json-editor
  13. visio中使用连接线连接形状
  14. linux+qt使用assimp库进行模型加载
  15. 英语学习软件——《经典双语广告语大全》(图)
  16. html在线表情聊天功能,HTML5高仿微信聊天、微信聊天表情|对话框|编辑器功能
  17. 快手自研直播多码率标准对行业发布
  18. XCTF新手练习区 writeup
  19. P1335 [NOI2013] 小Q的修炼 题解
  20. (转)[教你开启冻酸奶的app2sd] android2.2的APP TO SD功能启动方法

热门文章

  1. java的Arrays.fill()方法对二维数组赋值boolean类型
  2. 有些人为什么那么努力
  3. java项目开发实战--使用ssm框架开发众筹网站(IDEA版)
  4. CentOS 7 安装惠普打印机驱动
  5. AcceptEx浅析
  6. Android 接入银联支付
  7. 大学辍学、自学编程,GitHub 创始人是怎么号召 2800 万程序员的?
  8. locust-分布式部署-分布式性能压测
  9. 279.11G 大数据学习资料分享
  10. Linux Purify命令