1.由于C/C++运行库是在多线程应用程序出现的许多年前设计的,因此运行库中的大多数函数是为单线程应用程序设计的。
2.当应用程序第一次调用_tcstok_s的时候该函数将传入的字符串地址保存在它自己的静态变量中,当我们后来再传入NULL的时候,该函数会去引用保存下来的字符串地址。多线程情况下一个线程可能会覆盖另一个线程的静态变量,为了解决这个问题,C/C++ 运行库使用了TLS。
int main()
{
char strToken[] = "A BC,DE\\FGH";
const char strDelimit[] = " ,\\"; char *context = nullptr;
char *next_context = nullptr; context = strtok_s(strToken, strDelimit, &next_context); while (context != nullptr)
{
std::cout << context << std::endl;
context = strtok_s(nullptr, strDelimit, &next_context);
} getchar();
return ;
}
3.系统中的每个进程都有一组正在使用标志,每个标志可以被设为FREE或INUSE,表示该TLS元素是否正在使用。Microsoft保证至少有TLS_MINIMUN_AVAILABLE个标志可供使用。再WinNT.h中被定义为64,系统在需要的时候分配更多的TLS元素,最多可达1000多个!
动态TLS
1.DWORD TlsAlloc(); 这个函数让系统对进程中的位标志进行检索并找到一个FREE标志。然后系统会将该标志从FREE改为INUSE并返回该标志在数组中的索引,并且这个索引无论是进程中当前正在运行的线程,还是今后可能会创建的线程都不能再使用。TlsAlloc在返回之前,会遍历进程中的每个线程,并根据新分配的索引,在每个线程的数组中把对应的元素设为0。如果没有,则返回TLS_OUT_OF_INDEXES(0xFFFFFFFF)。
2.当系统创建一个线程的时候,会分配TLS_MINIMUM_AVAILABLE个PVOID值,将它们都初始化为0,并与线程关联起来,每个线程都有自己的PVOID数组,数组中的每个PVOID可以保存任意值。
3.BOOL TlsSetValue(DWORD dwTlsIndex, PVOID pvTlsValue); 把一个值放到线程的数组中,但是它无法修改另一个线程的TLS值。为了使得函数运行得尽可能快,牺牲了错误检查,即使传入得索引值不是由TlsAlloc分配得到的。
4.PVOID TlsGetValue(DWORD dwTlsIndex);只会查看属于调用线程得数组。
5.BOOL TlsFree(DWORD dwTlsIndex); 函数会将进程内的标志位数组中对应的INUSE标志重新设回FREE,还会将所有线程中该元素的内容设为0。
6.通常,如果DLL要使用TLS,会在DLL_PROCESS_ATTACH的时候调用TlsAlloc,在DLL_PROCESS_DETACH的时候调用TlsFree。
静态TLS
__declspec(thread) DWORD gt_dwStartTime;
1. __declspec(thread) 告诉编译器应该在可执行文件或DLL文件中,把对应的变量放到它自己的段中。__declspec(thread)后面的变量必须被声明为全局变量或静态变量。
2.当编译器对程序进行编译的时候,会将所有TLS变量放到它们自己的段中,这个段名为.tls。链接岂会将所有对象模块中的.tls段合并成一个大的.tls段,并将它保存到生成的可执行文件或DLL文件中。
3.当系统将应用程序载入到内存的时候,会查看可执行文件中的.tls段,并分配一块足够大的内存来保存所有的静态TLS变量。每当应用程序中的代码引用到这些变量之一时,相应的引用会被解析到刚分配的这块内存中的一个位置。
4.如果进程创建了另一个线程,那么系统会获知这一情况并自动分配另一块内存来保存新线程的静态TLS变量,新线程只能访问自己的静态TLS变量。
5.当系统载入应用程序的时候,会首先确定应用程序的.tls段的大小,并将它与应用程序链接的所有DLL的.tls段的大小相加。当系统在创建线程的时候,会自动分配一块足够大的内存来保存应用程序和所有隐式链接的DLL需要的TLS变量。