每个线程是没有自己单独的空间的,它们的所有的参数,变量都会放在该进程的空间里。为了实现局部变量人们引入了tls
用途:动态TLS和静态TLS这两项技术在创建DLL的时候更加有用,这是因为DLL通常并不知道它们被链接到的应用程序的结构是什么样的。
动态TLS
系统中每个进程都有一组正在使用标志(in-use flags),每个标志可以被设为FREE或INUSE,表示该TLS元素是否正在被使用。
进程中的线程是通过使用一个数组来保存与线程相关联的数据的,这个数组由TLS_MINIMUM_AVAILABLE个元素组成,在WINNT.H文件中该值被定义为64个。也就是说当线程创建时,系统给每一个线程分配了一个数组,这个数组共有TLS_MINIMUM_AVAILABLE个元素,并且将这个数组的各个元素初始化为0,之后系统把这个数组与新创建的线程关联起来。每一个线程中都有它自己的数组,数组中的每一个元素都能保存一个32位的值。在使用这个数组前首先要判定,数组中哪个元素可以使用,这将使用函数TlsAlloc来判断。函数TlsAlloc判断数组中一个元素可用后,就把这个元素分配给调用的线程,并保留给调用线程。要为数组中的某个元素赋值可以使用函数TlsSetValue,要得到某个元素的值可以使用TlsGetValue。
一般通过调用一组4个API函数来使用动态TLS:TlsAlloc、TlsSetValue、TlsGetValue和TlsFree。
调用TlsAlloc函数时会都多该进程标志位里面标志为FREE的一项,找到好把该标志位置为INUSE后得到该标志位的数组索引。
当系统创建一个线程的时候,会分配TLS_MINIMUM_AVAILABLE个PVOID值,将它们都初始化为0,并与线程关联起来。每个线程都有自己的PVOID数组,数组中的每个PVOID可以保存任意值。在能够将信息保存到线程的PVOID数组中之前,我们必须知道数组中的哪个索引可供使用—这就是调用TlsAlloc的目的。TlsAlloc为我们预定了一个索引,如果为2,即TlsAlloc返回值为2,那么无论是进程中当前正在运行的线程,还是今后可能会创建的线程,都不能再使用该索引2了
2)为了把一个值放到线程的PVOID数组中,应该调用TlsSetValue函数:
BOOL WINAPI TlsSetValue(
__in DWORD dwTlsIndex, //索引值,表示在数组中的具体位置
__in_opt LPVOID lpTlsValue //要设置的值
);
当一个线程调用TlsSetValue函数成功时,它会修改自己的PVOID数组,但它无法修改另一个线程的TLS值。在调用TlsSetValue时,我们应该总是传入前面在调用TlsAlloc时返回的索引。因为Windows为了效率牺牲了对输入值的错误检测。
3)为了从线程的数组中取回一个值,应该调用函数TlsGetValue:
LPVOID WINAPI TlsGetValue(
__in DWORD dwTlsIndex //索引值
);
这个函数会返回在索引为dwTlsIndex的TLS元素中保存的值。TlsGetValue只会查看属于调用线程的数组。
4)当不再需要一个已经预定的TLS元素时,应该调用TlsFree函数:
BOOL WINAPI TlsFree(
__in DWORD dwTlsIndex //索引值
);
这个函数告诉系统已经预定的这个TLS元素现在不需要了,函数会将进程内的位标志数组中对应的INUSE标志重新设回FREE。此外,函数还会将所有线程中该元素的内容设为0.
#include<windows.h>
#include<stdio.h>
#define THREADCOUNT 4
DWORD dwTlsIndex;
VOID ErrorExit(LPSTR);
VOID CommonFunc(VOID)
{
LPVOID lpvData;
lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
ErrorExit("TlsGetValue error");
printf("common:thread %d:lpvData=%lx\n", GetCurrentThreadId(), lpvData);
Sleep(5000);
}
DWORD WINAPI ThreadFunc(VOID)
{
LPVOID lpvData;
//为该线程初始化啊tls索引
lpvData = (LPVOID)LocalAlloc(LPTR, 256);//返回一个指向新地址指针的句柄,PTR为分配固定内存初始化内容为zero
if (!TlsSetValue(dwTlsIndex, lpvData))
ErrorExit("TlsSetVal error");
printf("thread %d:lpvoData=%lx", GetCurrentThreadId(), lpvData);
CommonFunc();
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL)lpvData);
return 0;
}
int main(VOID)
{
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;
if ((dwTlsIndex) = TlsAlloc() == TLS_OUT_OF_INDEXES)//在标志位数组里找到一个标志为free的值,返回它的数组索引,并为这个索引申请空间
ErrorExit("TlsAlock faild");
//创建多个线程
for (i = 0; i < THREADCOUNT; ++i)
{
hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc
, NULL, 0, &IDThread);
if (hThread[i] == NULL)
ErrorExit("CreatThread error\n");
}
for (i = 0; i < THREADCOUNT; ++i)
WaitForSingleObject(hThread[i], INFINITE);
}
/*
CreatThread函数
第一个参数:定义新线程安全属性
二:分配以字节数表示的线程堆栈的大小,默认值是0;
三:指向一个线程函数地址。每个线程都有自己的线程函数,线程函数是线程具体的执行代码
四:传递给线程函数的参数
五:表示创建线程的运行状态,其中CREATE_SUSPEND表示挂起当前创建的线程,而0表示立即执行当前创建的进程;
六:返回新创建的线程的ID编号
返回值:则返回新线程的句柄,调用WaitForSingleObject函数等待所创建线程的运行结束。函数的格式如下:
WaitForSingleObject()函数:
hHandle:指定对象或时间的句柄;
dwMilliseconds:等待时间,以毫秒为单位,当超过等待时间时,此函数返回。如果参数设置为0,则该函数立即返回;如果设置成INFINITE,则该函数直到有信号才返回。
*/
void ErrorExit(LPSTR lpszMessage)
{
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}