注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

C++11和LINUX-Win下的线程局部存储  

2016-05-11 18:26:43|  分类: C++(VC)编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

C++11LINUX-Win下的线程局部存储

线程局部存储的意思其实就是多个线程中的变量你是你的我是我的,咱们互不搭界,互不侵犯,最典型的就是Getlasterror,这个函数,当然它做的也不是多么好。

特别是在多个线程使用同一个线程函数时,这种情况会更普遍。在不同的平台下会有不同的方法,下面是在LinuxWindows下的实现:

一、Linux

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

int pthread_key_delete(pthread_key_t key);

void *pthread_getspecific(pthread_key_t key);

int pthread_setspecific(pthread_key_t key, const void *value);

二、Windows

内核方法 (Kernel32 API)

每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS索引数组),这个数组从C编程角度是隐藏着的不能直接访问(实际上该数组地址写入了线程信息块 thread information block,缩写TIBTEB),需要通过一些Kernel32 API函数调用访问。在进程内部创建、并发执行的各个线程,可以看作是执行相同动作(代码是一样的),但输入的数据不同,所以输出的结果数据也不同。因此各个线程使用的数据结构是相同的,只是有些变量是被所有的线程共享访问,为进程全局变量;另外一些变量是由每个线程独享访问,即线程局部存储。而每个线程局部存储的地址需要存入该线程的TLS索引数组。

举例说明:设每个线程都要使用线程私有的一个浮点型变量fvalue与一个长度为512个字节的缓冲区buf。需要在启动这些线程前,在主进程中先为fvaluebuf两个线程局部存储变量在TLS索引数组申请两个条目,假设为fvalue申请到第3号条目,为buf申请到第5号条目。也就是说,在任何一个线程内访问该线程私有的fvalue,需要查询该线程自己的TLS索引数组,其第3号条目存放的就是fvalue的地址。当然,启动各个线程后还需要为线程私有的fvaluebuf从堆中申请到存储空间,然后把fvaluebuf的地址登记入该线程的TLS索引数组的对应的第3号、第5号条目中,之后才能在该线程各处使用线程私有的fvaluebuf

第一步,在主进程内调用TlsAlloc()函数,从将要启动的每个线程的TLS索引数组中预定一个条目(slot),并返回该条目的序号:

DWORD global_dwTLS_fvalue = TLSAlloc();

注意,此步之后,变量( global_dwTLS_fvalue )保存的是分配得到的TLS索引数组的某个条目的序号,例如值为3。编程者在写这个程序代码时规定了这个变量( global_dwTLS_fvalue )保存了线程局部存储fvalue在每个线程的TLS索引数组的对应条目的序号。变量( global_dwTLS_fvalue )是普通的全局变量,各个线程随后只需要读取它的值。类似的,另外一个线程局部存储buf变量也需要定义一个变量( global_dwTLS_buf )并用TLSAlloc()初始化。

第二步,在每个进程执行的一开头,从堆中动态分配一块内存区域(使用LocalAlloc()函数调用)

void* p_fvalue = LocalAlloc(LPTRsizeof(float));

然后使用TlsSetValue()函数调用,把这块内存区域的地址存入TLS索引数组相应的条目中:

TlsSetValue( global_dwTLS_fvalue, p_fvalue);

第三步,在每个线程的任意执行位置,都可以通过该线程私有的TLS索引数组的相应条目,使用TlsGetValue()函数得到上一步的那块内存区域的地址,然后就可以对该内存区域做读写操作了。这就实现了在一个线程内部处处可访问的线程局部存储。

LPVOID lpvData = TlsGetValue(global_dwTLS_fvalue);

*lpvData = (float) 3.1416; //应用该线程局部存储

最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),这一般在线程即将结束时清理线程占用的各项资源时释放。然后,主进程从TLS索引数组中放弃对应的条目的占用(使用TlsFree()函数)。

LocalFree((HLOCAL) p_fvalue );

TlsFree(global_dwTLS_fvalue);

比较直白的方法:

直接声明这个变量是各个线程有自己拷贝的线程局部静态变量:

__declspec( thread ) int var_name;

但在VistaServer 2008之前的操作系统,仅限于在应用程序的主进程(.exe)以及与主进程一起装入内存的动态连接库(.dll),才能正常装入本方法所声明的线程静态存储。

 

因为它与平台相关,所以其移植性可见是非常差的。但大家也不必太担心,新的C++11提供了类似的局部存储,不过需要比较高级版本的编译器,比如在VS2010上用着都是不是很痛快。

C++11 Thread的线程本地存储(Thread Local Storage

TLS(thread local storage)类似,该功能允许你声明一个带有thread_local的声明符的变量。这意味着,每一个线程都有自己的该全局变量的实例(instance),该实例的变量名就是全局变量名称。

以前:

int j = 0;

void foo()

  {

  m.lock();

  j++;

  m.unlock();

  }

void func()

  {

  j = 0;

  std::thread t1(foo);

  std::thread t2(foo);

  t1.join();

  t2.join();

 // j = 2;

}

现在看:

thread_local int j = 0;

void foo()

  {

  m.lock();

  j++; // j is now 1, no matter the thread. j is local to this thread.

  m.unlock();

  }

void func()

  {

  j = 0;

  std::thread t1(foo);

  std::thread t2(foo);

  t1.join();

  t2.join();

 // j still 0. The other "j"s were local to the threads

}

苟日新,日日新。

 

 

 

 

 

 

  评论这张
 
阅读(271)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017