加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

多线程(C++)线程局部存储TLS

(2011-07-20 17:55:56)
标签:

多线程

tls

局部存储

杂谈

分类: 多线程

多线程之线程局部存储

一 线程局部存储 (TLS)
      来自:http://msdn2.microsoft.com/en-us/library/ms686749.aspx
      
      同 一进程中的所有线程共享相同的虚拟地址空间。不同的线程中的局部变量有不同的副本,但是static和globl变量是同一进程中的所有线程共享的。使用 TLS技术可以为static和globl的变量,根据当前进程的线程数量创建一个array,每个线程可以通过array的index来访问对应的变 量,这样也就保证了static和global的变量为每一个线程都创建不同的副本。

二 线程局部存储(TLS)实现使用

1)TLS 的 API 实现
通过 Win32 API 层和编译器实现“线程本地存储”。有关详细信息,请参见 Win32 API 文档中的 TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree。 (下面的代码对msdn的稍加修改)

http://www.cppblog.com/Images/OutliningIndicators/None.gif#include <windows.h> 
http://www.cppblog.com/Images/OutliningIndicators/None.gif#include 
<stdio.h> 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gif
#define THREADCOUNT 10
http://www.cppblog.com/Images/OutliningIndicators/None.gifDWORD dwTlsIndex;  
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gif
static int g_x = 100 // test static var for multiple threading
http://www.cppblog.com/Images/OutliningIndicators/None.gif

http://www.cppblog.com/Images/OutliningIndicators/None.gifVOID ErrorExit(LPSTR); 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gifVOID CommonFunc(VOID) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    LPVOID lpvData; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Retrieve data pointer for the current thread. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    lpvData = TlsGetValue(dwTlsIndex); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
if ((lpvData == 0&& (GetLastError() != ERROR_SUCCESS)) 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        ErrorExit(
"TlsGetValue error"); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
int *pg_x = (int*)lpvData;
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Use the data stored for the current thread. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    printf("thread %d: g_x adress=%lx,g_x copy ++ %d\n"GetCurrentThreadId(), pg_x, *pg_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    Sleep(
1000); 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}
 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gifDWORD WINAPI ThreadFunc(VOID) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    LPVOID lpvData; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Initialize the TLS index for this thread. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    lpvData = (LPVOID) LocalAlloc(LPTR, 256); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
//*(int*)lpvData g_x;
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    int *pg_x = (int*)lpvData;
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
*pg_x = g_x;
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
if (! TlsSetValue(dwTlsIndex, lpvData)) 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        ErrorExit(
"TlsSetValue error");     
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    printf(
"thread %d: g_x adress=%lx,g_x copy %d\n"GetCurrentThreadId(), pg_x, *pg_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    InterlockedExchangeAdd(reinterpret_cast
<long*>(pg_x),1);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    CommonFunc(); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Release the dynamic memory before the thread returns.
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    lpvData = TlsGetValue(dwTlsIndex); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
if (lpvData != 0
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        LocalFree((HLOCAL) lpvData); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
return 0
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}
 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gif
int main(VOID) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    DWORD IDThread; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    HANDLE hThread[THREADCOUNT]; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
int i; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    printf(
"main thread: g_x is :%d\n",g_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Allocate TLS index.  
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        ErrorExit(
"TlsAlloc failed"); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
//test for multiple static or global var
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    int dwTlsIndex2 = TlsAlloc();
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
// Create multiple threads. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
    for (i = 0< THREADCOUNT; i++
http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif    

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        hThread[i] 
= CreateThread(NULL, // default security attributes 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
            0                          // use default stack size 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
            (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
            NULL,                    // no thread function argument 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
            0                      // use default creation flags 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
            &IDThread);              // returns thread identifier 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        
// Check the return value for success. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
        if (hThread[i] == NULL) 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif            ErrorExit(
"CreateThread error\n"); 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif    }
 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
for (i = 0< THREADCOUNT; i++
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif        WaitForSingleObject(hThread[i], INFINITE); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    TlsFree(dwTlsIndex);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    printf(
"main thread: g_x is :%d\n",g_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    
return 0
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}
 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gifVOID ErrorExit (LPSTR lpszMessage) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    fprintf(stderr, 
"%s\n"lpszMessage); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    ExitProcess(
0); 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}

运行结果:(可以看出不同的线程中的g_x变量有不同的地址,即有不同的拷贝,主线程变量在开始和最后都不被改变。)
http://www.cppblog.com/images/cppblog_com/mzty/2021/o_tls1.JPG

PS: (TLS的实现原理与API解释)
1:在多线程的进程中,为每一个static或是global变量创建一个void*的数组,使变量不同线程都有一个拷贝,然后拷贝的指针放入Void*的数组中。
2:TlsAlloc() 得到对应于一个static或global变量所对应的void*的数组的索引,这个用来标示不同static或global变量所对应的void*的数组。
3:LocalAlloc()用来分配变量在此线程的拷贝的指针。
4:TlsSetValue()用来把刚分配的指针加到所对应的void*的数组中,加入后一般对会对此指针赋值供此线程使用。
5:TlsGetValue()用来从对应的void*的数组中找到此线程所对应的拷贝的指针。
6:   TlsFree() 释放整个void*的指针数组。



2)TLS 的编译器实现

为了支持 TLS,已将新属性 thread 添加到了 C 和 C++ 语言,并由 Visual C++ 编译器支持。使用 __declspec 关键字声明 thread 变量。例如,以下代码声明了一个整数线程局部变量,并用一个值对其进行初始化:

__declspec( thread ) int tls_i = 1;

下 面的代码使用了VC提供的__declspec关键字来实现TLS,如果不使用TLS,全局变量g_x则在线程中输出的结果都是1,2,3,4.。。。但 是如果使用TLS则在线程中输出的结果都是1,编译器帮我们保证了每个线程都有一个副本。 我们还可以看到主线程在开始和最后输出的g_x都是0,这也更 说明了在线程有不同的副本。

http://www.cppblog.com/Images/OutliningIndicators/None.gif#include <windows.h> 
http://www.cppblog.com/Images/OutliningIndicators/None.gif#include 
<stdio.h> 
http://www.cppblog.com/Images/OutliningIndicators/None.gif 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
#define THREADCOUNT 10
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gifDWORD dwTlsIndex; 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gif
//static int g_x 0;
http://www.cppblog.com/Images/OutliningIndicators/None.gif

http://www.cppblog.com/Images/OutliningIndicators/None.gif
#define Thread  __declspec(thread)
http://www.cppblog.com/Images/OutliningIndicators/None.gifThread 
static int g_x = 0;
http://www.cppblog.com/Images/OutliningIndicators/None.gif
http://www.cppblog.com/Images/OutliningIndicators/None.gifVOID ErrorExit(LPSTR); 
http://www.cppblog.com/Images/OutliningIndicators/None.gif 
http://www.cppblog.com/Images/OutliningIndicators/None.gifDWORD WINAPI ThreadFunc(VOID) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif  InterlockedExchangeAdd(reinterpret_cast
<long*>(&g_x),1); //g_x+=1;   
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
  printf("thread id: %d, g_x++ %d, g_x adress %d\n",GetCurrentThreadId(),g_x,&g_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif  
return 1
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}
 
http://www.cppblog.com/Images/OutliningIndicators/None.gif 
http://www.cppblog.com/Images/OutliningIndicators/None.gif
int main(VOID) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   DWORD IDThread; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   HANDLE hThread[THREADCOUNT]; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   
int i; 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif    printf(
"main thread: g_x %d, g_x adress %d\n",g_x,&g_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif  
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
// Create multiple threads.  
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
   for (i = 0< THREADCOUNT; i++
http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif   

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif      hThread[i] 
= CreateThread(NULL, // default security attributes 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
         0                          // use default stack size 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
         (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
         NULL,                    // no thread function argument 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
         0                      // use default creation flags 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
         &IDThread);              // returns thread identifier 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   
// Check the return value for success. 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
      if (hThread[i] == NULL) 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif         ErrorExit(
"CreateThread error\n"); 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif   }
 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   
for (i = 0< THREADCOUNT; i++
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif      WaitForSingleObject(hThread[i], INFINITE); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif  
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   printf(
"main thread: g_x %d, g_x adress %d\n",g_x,&g_x);
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   
return 0
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif}
 
http://www.cppblog.com/Images/OutliningIndicators/None.gif 
http://www.cppblog.com/Images/OutliningIndicators/None.gifVOID ErrorExit (LPSTR lpszMessage) 
http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif

http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   fprintf(stderr, 
"%s\n"lpszMessage); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif   ExitProcess(
0); 
http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif

运行结果:
http://www.cppblog.com/images/cppblog_com/mzty/2021/o_tls2.JPG
三 使用VC关键字实现TLS需要注意:

声明静态绑定线程的本地对象和变量时必须遵守下列原则:

  • thread 属性只能应用于数据声明和定义。它不能用于函数声明或定义。例如,以下代码将生成一个编译器错误:
    #define Thread  __declspec( thread )
        Thread void func();     // This will generate an error.
    
  • 只能在具有 static 作用域的数据项上指定 thread 修饰符。包括全局数据对象(包括 staticextern)、本地静态对象和 C++ 类的静态数据成员。不可以用 thread 属性声明自动数据对象。以下代码将生成编译器错误:
    #define Thread  __declspec( thread )
        void func1()
        {
        Thread int tls_i;            // This will generate an error.
        }
        int func2( Thread int tls_i )    // This will generate an error.
        {
        return tls_i;
        }
    
  • 线程本地对象的声明和定义必须全都指定 thread 属性。例如,以下代码将生成错误:
    #define Thread  __declspec( thread )
        extern int tls_i;        // This will generate an error, since the
        int Thread tls_i;        // declaration and definition differ.
    
  • thread 属性不能用作类型修饰符。例如,以下代码将生成一个编译器错误:
    char __declspec( thread ) *ch;        // Error
    
  • C++ 类不能使用 thread 属性。但是,可以使用 thread 属性将 C++ 类对象实例化。例如,以下代码将生成一个编译器错误:
    #define Thread  __declspec( thread )
        class Thread C       // Error: classes cannot be declared Thread.
        {
        // Code
        };
        C CObject;
    

    因为允许使用 thread 属性的 C++ 对象的声明,因此下面两个示例在语义上是等效的:

    #define Thread  __declspec( thread )
        Thread class B
        {
        // Code
        } BObject;               // OK--BObject is declared thread local.
        class B
        {
        // Code
        };
        Thread B BObject;        // OK--BObject is declared thread local.
    
  • 不将线程本地对象的地址视为常数,并且涉及此类地址的任何表达式都不视为常数。在标准 C 中,这种作法的效果是禁止将线程本地变量的地址用作对象或指针的初始值设定项。例如,C 编译器将以下代码标记为错误:
    #define Thread  __declspec( thread )
        Thread int tls_i;
        int *p = &tls_i;       //This will generate an error in C.
    

    但是,此限制不适用于 C++。因为 C++ 允许动态初始化所有对象,因此可以用使用线程本地变量地址的表达式初始化对象。实现此操作的方式与实现线程本地对象结构的方式相同。例如,以上显示的代码 在作为 C++ 源文件编译时不会生成错误。请注意:只有在其中获取地址的线程仍然存在的情况下,线程本地变量的地址才有效。

  • 标准 C 允许使用涉及引用自身的表达式初始化对象或变量,但只适用于非静态作用域的对象。虽然 C++ 通常允许使用涉及引用自身的表达式动态初始化对象,但是这种类型的初始化不允许用于线程本地对象。例如:
    #define Thread  __declspec( thread )
        Thread int tls_i = tls_i;                // Error in C and C++
        int j = j;                               // OK in C++, error in C
        Thread int tls_i = sizeof( tls_i )       // Legal in C and C++
    

    请注意:包含正在初始化的对象的 sizeof 表达式不建立对自身的引用且在 C 和 C++ 中都是合法的。

    C++ 不允许此类对线程数据的动态初始化,因为将来可能要对线程本地存储功能进行增强。

  • 如果 DLL 将任何非本地数据或对象声明为 __declspec(线程),动态加载该 DLL 时会导致保护错误。使用 LoadLibrary 加载所有 DLL 后,每当代码引用非本地 __declspec(线程)数据时,将导致系统故障。由于线程的全局变量空间是在运行时分配的,因此此空间的大小是以应用程序的需求和所有静态链接的 DLL 的需求相加为基础计算出来的。使用 LoadLibrary 时,无法扩展此空间以允许放置用 __declspec(线程)声明的线程本地变量。如果 DLL 可能是用 LoadLibrary 加载的,请在 DLL 中使用 TLS API(如 TlsAlloc)来分配 TLS。
四  DLL使用TLS :http://msdn2.microsoft.com/en-us/library/ms686997.aspx

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有