10.5.3 declspec(thread)和TLS
在我们处理这些问题之前,我想先描述一个TSS机制,该机制被Win32平台上的大多数编译器所支持,用于简化对Win32 TLS函数的使用。编译器允许你对变量定义使用__declspec(thread)修饰符,像这样:
- __declspec(thread) int x;
现在x就是线程相关的了,每个线程都会得到一份自己的拷贝。编译器将此类变量置于目标文件的.tls节中,连接器则将各目标文件中的.tls节合并到一起。当操作系统加载进程时,会去寻找可执行文件的.tls节并创建一个线程相关的内存块来存放该节。每当诞生一个线程时,操作系统都会进行这个步骤,为新生线程创建一个相关的内存块(TLS块)。
遗憾的是,这种策略虽然极其高效[Wils2003d],但有一个巨大的缺陷,导致它只适合用于可执行文件,而不适合动态库。它可以被用于隐式连接的动态库,从而在进程的加载期被加载,因为操作系统可以对所有在应用程序加载时加载的连接单元分配线程相关的块。问题在于,如果一个包含了.tls节的动态库在进程已执行一段时间后被显式加载,操作系统无法回去对所有现存线程的线程相关块进行扩展,因此你的库将会加载失败。
我认为最好在任何DLL中都避免使用__declspec(thread),即使是那些你认为总是会被隐式连接的DLL。在现代的基于组件的世界里,完全存在这样一种可能:某个DLL被隐式地连接到一个组件,但该组件却是由某个可执行文件来显式加载的,这个可执行文件又是由另一个编译器创建的,甚至是用另一门语言编写的,并且,它先前并没有(隐式)加载过你的DLL。你的DLL无法被加载,依赖于它的组件也无法被加载。