线程局部存储(thread-local storage, TLS)。利用TLS机制可以为进程中所有的线程关联若干个数据,各个线程通过由TLS分配的全局索引来访问与自己关联的数据。这样,每个线程都可以有线程局部的静态存储数据。
1、 TLS是一种机制,每个线程可以持有一个指针,指向它自己的一份数据结构拷贝。(注意是为每个线程拷贝一份,它们是独立无关的
2、MFC使用TLS来追踪每个线程所使用的GDI对象和USER对象
3、TLS对的运作方式是。每个线程有一个由4字节槽(SLOTS)组成的数组,这个数组保证至少有TLS_MINIMUM_AVAILABLE个槽在其中,目前的操作系统保证至少有64个槽,每个槽可以指定放置任何特殊结构。如果结构WORDCOUNT放在槽4,那么每个线程可以配置一个WORDCOUNT结构空间,并设定让TLS槽4指向它
4、设定TLS
DWORD TlsAlooc(VOID);//在TLS数据中配置一个槽,并传回数据索引
    成功,传回TLS数组中的一个槽,在DLL之中,典型的用法在收到DLL_PROCESS_ATTACH时调用此函数,调用一次只能获得一个槽,可以把所有线程都可以使用的那些全局变量指定到配置而来的槽之中。
5、你只要配置一个槽就好,它可以被所有数据结构使用,线程的个数有限。
如果你有数个结构,把它们封装在一个大结构中,然后,为TLS线程设定一个值,给目前线程使用。
我们使用的函数是TlsSetValue(),规格如下
BOOL TlsSetValue(
DWORD dwTLsIndex,//由TLSALLOC传回的槽索引
LPVOID lpTlsValue//要储存到上述槽中的数据
)
 (1)当DLL获得DLL_PROCESS_ATTACH时,调用TLSALLOC,设传回4。DLL把此值储存于一个全局变量中,然后配置一块内存,假设在0X1F0000处,DLL调用TLSSETVALUE让槽4和地址0X1F0000产生关系(仅仅针对该线程)
 (2)当下一线程附着到同一个DLL时,DLL知道已经有一个槽被配置过了,因为全局变量中已经有值(槽编号),所以它再配置一块内存,假设在OX203000处,DLL调用TLSSETVALUE,让槽4与地址OX203000产生关联,仅与这个线程有关,与上个线程
无关
6、取得槽内容:
LPVOID TlsGetValue(
DWORD dwTLsIndex
);
返回设定在槽中的值
7、释放槽
当DLL最终获得了DLL_PROCESS_DETACH,就释放
BOOL TlsFree(
DWORD dwTLsIndex
);
8、一个线程不能处理另一个线程的TLS槽,一个TLS索引只在同一进程有效
9、使用_declspec(thread),可以保证对每个线程独一无二,具有线程局部性
如DLL中有以下声明
DWORD gprogresscounter;
如果这样
_declspec(thread) DWORD gprogresscounter;保证对每个线程唯一
也可以用在结构体上
struct _sostruct
{
......
......
}
_declspec(thread) struct _sostruct abc;
将数据或结构体放在TLS上,是编译器的任务,每个以这种方式声明对象的EXE和DLL,将在可执行文件中有一个特殊的节区,内含所有的线程局部变量
   C++中如果使用_declspec(thread)在DLL中,没有办法被loadlibrary载入。
 
**********************************动态使用TLS的例子***************************************************

利用TLS可以给特定的线程关联一个数据。比如下面的例子将每个线程的创建时间与该线程关联了起来,这样,在线程终止的时候就可以得到线程的生命周期。整个跟踪线程运行时间的例子的代码如下:

#include <stdio.h>                                  

#include <windows.h>            

#include <process.h>

// 利用TLS跟踪线程的运行时间

DWORD g_tlsUsedTime;    //全局变量

void InitStartTime();  //获取线程启动的时间

DWORD GetUsedTime(); //获取线程的生命周期

UINT __stdcall ThreadFunc(LPVOID) 

{       int i;

         // 初始化开始时间

         InitStartTime();

         // 模拟长时间工作

         i = 10000*10000+1;

         while(--i){}

         // 打印出本线程运行的时间

         printf(" This thread is coming to end. Thread ID: %-5d, Used Time: %d "n",

                    ::GetCurrentThreadId(), GetUsedTime());

         return 0;

}

int main(int argc, char* argv[])

{       UINT uId;

         int i;

         HANDLE h[10];

         // 通过在进程位数组中申请一个索引,初始化线程运行时间记录系统

         g_tlsUsedTime = ::TlsAlloc();

         // 令十个线程同时运行,并等待它们各自的输出结果

         for(i=0; i<10; i++)

         {       h[i] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId);         }

         for(i=0; i<10; i++)

         {       ::WaitForSingleObject(h[i], INFINITE);

                   ::CloseHandle(h[i]);      }

         // 通过释放线程局部存储索引,释放时间记录系统占用的资源

         ::TlsFree(g_tlsUsedTime);

         return 0;

}

// 初始化线程的开始时间

void InitStartTime()

{       // 获得当前时间,将线程的创建时间与线程对象相关联

         DWORD dwStart = ::GetTickCount();

         ::TlsSetValue(g_tlsUsedTime, (LPVOID)dwStart);

}

// 取得一个线程已经运行的时间

DWORD GetUsedTime()

{       // 获得当前时间,返回当前时间和线程创建时间的差值

         DWORD dwElapsed = ::GetTickCount();

         dwElapsed = dwElapsed - (DWORD)::TlsGetValue(g_tlsUsedTime);

         return dwElapsed;

}

注:GetTickCount函数可以取得Windows从启动开始经过的时间,其返回值是以毫秒为单位的已启动的时间。

一般情况下,为各线程分配TLS索引的工作要在主线程中完成,而分配的索引值应该保存在全局变量中,以方便各线程访问。上面的例子代码很清除地说明了这一点。主线程一开始就使用TlsAlloc为时间跟踪系统申请了一个索引,保存在全局变量g_tlsUsedTime中。之后,为了示例TLS机制的特点同时创建了10个线程。这10个线程最后都打印出了自己的生命周期,如图所示。