线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不须要锁来维护。。windows
所以也没有多线程间资源竞争问题,那如何去实现TLS存储呢,主要有如下几种方式:api
__thread
修饰符__declspec(thread)
修饰符pthread_setspecific
和pthread_getspecific
接口TlsSetValue
和TlsGetValue
其中__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数据维护,须要手动管理相关内存的释放问题,用的很差很容易致使内存泄露。。code
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, 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这些老系统就用不了了。。 这里就很少描述了。
thread_local
接口封装最近对tbox的tls接口进行了改造,而且重构了实现逻辑,在剪口易用性、功能性以及效率上都获得了很大的提高。。
目前支持如下功能:
用起来也很方便,很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开源工程