线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护。。

因此也没有多线程间资源竞争问题,那如何去实现TLS存储呢,主要有以下几种方式:

  1. gcc和clang的__thread修饰符
  2. windows下msvc的__declspec(thread)修饰符
  3. pthread库pthread_setspecificpthread_getspecific接口
  4. windows下的TlsSetValueTlsGetValue

__thread和__declspec(thread)的使用

其中__thread和__declspec(thread)用起来最为方便,只需要在static或者全局变量前加上此修饰符,然后在线程里面访问变量就行了

例如:

tb_void_t tb_thread_func(tb_cpointer_t priv)
{
// 定义一个线程局部变量
static __thread int a = 0; // 初始化这个变量,设置为当前线程id
if (!a) a = tb_thread_self();
}

如果运行多个线程的话,上述代码中每个线程的变量a的值,都是不相同的,值为每个线程的id

__declspec(thread) 用起来也是类似,只需替换下 __thread就行了

虽然这两个修饰符用起来很方便,但是需要编译器支持,虽然现在大部分平台的编译器都已支持,但是作为跨平台开发,这样还是不够的

毕竟还是有不少低版本gcc,不一定支持__thread,尤其是嵌入式开发领域,交叉编译工具链中的编译器支持力度差异还是蛮大的。。

另外,使用__thread进行tls数据维护,需要手动管理相关内存的释放问题,用的不好很容易导致内存泄露。。

pthread接口

pthread 的 tls 相关接口,比较完善,并且支持注册free函数,在线程退出的时候,自动释放相关tls数据,避免内存泄露,但是使用上稍显复杂了些

我们看个简单的例子:

// 测试线程中tls变量存储的key,需定义为全局或者static
static pthread_key_t g_local_key = 0; static tb_void_t tb_thread_local_free(tb_pointer_t priv)
{
tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}
static tb_void_t tb_thread_local_init(tb_void_t)
{
// 创建tls的key,并且设置自动释放函数
pthread_key_create(&g_local_key, tb_thread_local_free);
}
static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
// 在所有线程中,仅执行一次,用于在线程内部初始化 tls 的 key
static pthread_once_t s_once = PTHREAD_ONCE_INIT;
pthread_once(&s_once, tb_thread_local_init); // 尝试读取当前tls数据
tb_size_t local;
if (!(local = (tb_size_t)pthread_getspecific(g_local_key)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (0 == pthread_setspecific(g_local_key, (tb_pointer_t)self))
local = self;
} return 0;
}

看上去复杂了些,但是更加灵活,如果不需要在线程内部创建key的话,就不需要调用pthread_once了,直接把创建好的key传入线程内部去访问就好。

TlsSetValue 接口

此套接口(TlsSetValue, TlsGetValue, TlsAlloc, TlsFree),属于windows的tls操作接口,当然不能跨平台了,使用起来和pthread的差不多,但是无法注册自动释放函数,并且也没提供类似pthread_once的接口

在线程内部自创建key,功能上稍显不足。。

static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
// 创建一个tls的key,注:此处非线程安全,最好放到类似pthread_once提供的init函数中去创建
// 此处就临时先这么写了,仅仅只是为了方便描述api用法,不要照搬哦。。
static DWORD s_key = 0;
if (!s_key) s_key = TlsAlloc(); // 尝试读取当前tls数据
DWORD local;
if (!(local = TlsGetValue(s_key)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (TlsSetValue(s_key, (LPVOID)self))
local = self;
} return 0;
}

其实windows上还提供了FlsAlloc, FlsSetValue系列接口,给协程使用,并且支持注册自动释放的回调函数,不过对系统版本有些要求,像xp这些老系统就用不了了。。

这里就不多描述了。

tbox提供的thread_local接口封装

最近对tbox的tls接口进行了改造,并且重构了实现逻辑,在剪口易用性、功能性以及效率上都得到了很大的提升。。

目前支持以下功能:

  • 支持注册自动释放回调,保证在线程退出时,自动释放设置的tls数据
  • 支持在线程内部进行线程安全的key创建
  • tbox退出时会自动销毁所有创建的key,当然也可以提前主动销毁它

用起来也很方便,很pthread很类似,但是内部自动调用了pthread_once,不用想pthread那样显式的去调用它了,例如:

static tb_void_t tb_demo_thread_local_free(tb_cpointer_t priv)
{
tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}
static tb_int_t tb_demo_thread_local_test(tb_cpointer_t priv)
{
/* 线程安全地初始化一个tls对象,相当于key,并且注册自动free回调
*
* 注:虽然所有线程都会执行到这个tb_thread_local_init
* 但是s_local的tls对象,只会确保初始化一次,内部有类似pthread_once接口来维护
*/
static tb_thread_local_t s_local = TB_THREAD_LOCAL_INIT;
if (!tb_thread_local_init(&s_local, tb_demo_thread_local_free)) return -1; // 尝试读取当前tls数据
tb_size_t local;
if (!(local = (tb_size_t)tb_thread_local_get(&s_local)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (tb_thread_local_set(&s_local, (tb_cpointer_t)self))
local = self;
} return 0;
}

在线程退出时,它会自动调用free回调,释放对应残留的tls数据,并且在tb_exit退出后,销毁所有创建的tls对象

当然你可以可以主动调用:tb_thread_local_exit(&s_local) 来销毁它。。

tbox的这套接口,相比pthread减少了一个init的回调函数,比windows那套多了自动释放的机制,并且同时支持跨平台。。

杂谈

之前我看到一些库中,对__thread和pthread接口进行了混用,感到很是莫名,个人感觉是有问题的,例如:

static __thread pthread_key_t g_key;

原本pthread文档中就明确表述key需要全局或者static存储,而这里加上__thread后,其实每个线程访问的key都不是同一个key了哦。。

总结

如果仅仅只是想用来在线程内部存储一些简单的int数据,并且不考虑完备的跨平台支持,那么建议直接使用__thread或者__declspec(thread)j就行了,非常方便易用

如果要考虑跨平台操作的话,tbox的tls接口也是个不错的选择哦。。


个人主页:TBOOX开源工程

原文出处:http://tboox.org/cn/2016/09/28/thread-local/

线程局部存储tls的使用的更多相关文章

  1. 【windows核心编程】线程局部存储TLS

    线程局部存储TLS, Thread Local Storage TLS是C/C++运行库的一部分,而非操作系统的一部分. 分为动态TSL 和 静态TLS 一.动态TLS 应用程序通过调用一组4个函数来 ...

  2. 线程局部存储TLS

    1 .使用线程局部存储的理由 当我们希望这个进程的全局变量变为线程私有时,而不是所有线程共享的,也就是每个线程拥有一份副本时,这时候就可以用到线程局部存储(TLS,Thread Local Stora ...

  3. 线程局部存储 TLS

    C/C++运行库提供了TLS(线程局部存储),在多线程还未产生时,可以将数据与正在执行的线程关联.strtok()函数就是一个很好的例子.与它一起的还有strtok_s(),_tcstok_s()等等 ...

  4. 线程局部存储TLS(thread local storage)

    同一全局变量或者静态变量每个线程访问的是同一变量,多个线程同时访存同一全局变量或者静态变量时会导致冲突,尤其是多个线程同时需要修改这一变量时,通过TLS机制,为每一个使用该全局变量的线程都提供一个变量 ...

  5. linux中的线程局部存储(TLS)

    http://blog.csdn.net/cywosp/article/details/26469435

  6. PE格式第八讲,TLS表(线程局部存储)

    PE格式第八讲,TLS表(线程局部存储) 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶复习线程相关知识 首先讲解 ...

  7. 线程局部存储(TLS)

    线程局部存储(TLS) 2011-10-11 09:59:28|  分类: Win32---API |  标签:tls   |举报 |字号 订阅   什么是线程局部存储 众所周知,线程是执行的单元,同 ...

  8. 【C# 线程】线程局部存储(TLS)理论部分 ThreadStatic|LocalDataStoreSlot|ThreadLocal<T>

    线程本地存储(TLS:Thread Local Storage) 线程本地存储(Thread Local Storage),字面意思就是专属某个线程的存储空间.变量大体上分为全局变量和局部变量,一个进 ...

  9. 基于TLS(线程局部存储)的高效timelog实现

    什么是timelog? 我们在分析程序性能的时候,会加入的一些logging信息记录每一部分的时间信息 timelog模块的功能就是提供统一的接口来允许添加和保存logging 我们正在用的timel ...

随机推荐

  1. ES6判断当前页面是否微信浏览器中打开

    1.使用jq判断是否用微信浏览器打开页面 var is_weixin = (function(){return navigator.userAgent.toLowerCase().indexOf('m ...

  2. Keyboarding (bfs+预处理+判重优化)

    # #10030. 「一本通 1.4 练习 2」Keyboarding [题目描述] 给定一个 $r$ 行 $c$ 列的在电视上的"虚拟键盘",通过「上,下,左,右,选择」共 $5 ...

  3. 最长回文子序列/最长回文子串(DP,马拉车)

    字符子串和字符子序列的区别 字符字串指的是字符串中连续的n个字符:如palindrome中,pa,alind,drome等都属于它的字串 而字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符: ...

  4. C Yuhao and a Parenthesis

    Yuhao and a Parenthesis time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  5. 1897. tank 坦克游戏

    传送门 显然考虑 $dp$,发现时间只和当前位置和攻击次数有关,设 $F[i][j][k]$ 表示当前位置为 $i,j$ ,攻击了 $k$ 次得到的最大分数 初始 $f[1][1][k]$ 为位置 $ ...

  6. UDP千兆光通信(一)、整体认知与概述

    前几天看到群里的一个前辈说FPGA的入门就是搭建一个完整的以太网通信平台,颇有感触,就在博客平台上搭建一个以太网平台,包含多个模块,先整体驯熟一下实现的功能,再分别叙述各个模块的功能与实现. 整体功能 ...

  7. Add JWT Bearer Authorization to Swagger and ASP.NET Core

    Add JWT Bearer Authorization to Swagger and ASP.NET Core     If you have an ASP.NET Core web applica ...

  8. websocket无法注入bean问题解决方案

    websocket服务端往往需要和服务层打交道,因此需要将服务层的一些bean注入到websocket实现类中使用,但是呢,websocket实现类虽然顶部加上了@Component注解,依然无法通过 ...

  9. wepy_two

    2.代码高亮WebStorm/PhpStorm(其他工具参见:wepy官网代码高亮) (1)打开Settings,搜索Plugins,搜索Vue.js插件并安装. (2) 打开Settings,搜索F ...

  10. 2018-11-19-visualStudio-无法登陆

    title author date CreateTime categories visualStudio 无法登陆 lindexi 2018-11-19 15:24:15 +0800 2018-2-1 ...