所谓堆栈,相当于是一个历史记录,里面存储的数据(函参、返回值、调用上下文)与函数调用顺序有着密不可分的联系。假设有某线程ThreadProc的伪码是这样写滴:
int a=100;
CreateThread(ThreadProc,NULL);
void ThreadProc(void* lpvoid) {
print(a++);
A();
}
void A() {
print(a++);
B();
}
void B() {
print(a++);
C();
}
void C() {
print(a++);
}
CreateThread(ThreadProc,NULL);
void ThreadProc(void* lpvoid) {
print(a++);
A();
}
void A() {
print(a++);
B();
}
void B() {
print(a++);
C();
}
void C() {
print(a++);
}
其调用链很清楚,ThreadProc->A->B->C然后再返回,调用链中的每个函数都引用了a并且修改了a的值,注意到,“全局变量”a是没有存储在线程堆栈中的,但是与此同时我们想让a成为线程所独有的数据,即,不期望发生同时多个ThreadProc线程的实例访问并修改同一个全局变量,导致运行结果不确定的情况(运行结果的不确定源于线程调度顺序、调度时被中断位置的不确定),该怎么办?用TLS。
慢,事情还没有完!隔壁的某位先生马上抢白我了(绝杀状):“上述解释还是无法令人满意!——这个程序我还可以这样写,照样不需要TLS!” 下面一起来看看这个无需TLS的程序是怎么写的:
int a=100;
CreateThread(ThreadProc,&a);
void ThreadProc(void* lpvoid) {
int aa=*((int*)lpvoid);
int* pa=&aa;
print((*pa)++);
A(pa);
}
void A(int* pa) {
print((*pa)++);
B(pa);
}
void B(int* pa) {
print((*pa)++);
C(pa);
}
void C(int* pa) {
print((*pa)++);
}
CreateThread(ThreadProc,&a);
void ThreadProc(void* lpvoid) {
int aa=*((int*)lpvoid);
int* pa=&aa;
print((*pa)++);
A(pa);
}
void A(int* pa) {
print((*pa)++);
B(pa);
}
void B(int* pa) {
print((*pa)++);
C(pa);
}
void C(int* pa) {
print((*pa)++);
}
汗....本来想偷点懒de.....^^ 没办法了,看来偶也得放绝杀了。是的,我承认隔壁的先生的确是善于思考,通过copy一份全局变量a的值到线程自己的私有局部变量aa、同时在调用每一个函数时都将aa的指针作为函参的方法,几乎是完全避免了TLS的使用,也就否定了TLS存在的意义。不过,(嘿嘿,准备逆转)难道大家不觉得这种方式太烦了吗?且不论仅仅只用到了一个线程全局量的时候就这样了,更不用说假设要用到十个八个全局量的时候场面有多壮观了;还有,调用链上每个函数都有一个函参指针指向堆栈copy,假设是十个八个全局量、调用链又有个十层八层那该浪费多少堆栈里面的空间啊.....另外,再补充一点,为了向下兼容,例如,现有的C标准库在Win32之前已经存在很长一段时间了,由于没有考虑多线程环境的影响,里面大量地使用了全局静态变量,C标准库不是由你我写的,当然也就没法修改了,如果在线程体里面要使用这些“危险”函数的话,你我该怎么办?不用标准库?会被累死的......所以C/C++标准库使用了TLS,所以TLS有其存在的价值。
隔壁的先生不啃声了。一句话,存在,总得有道理=)