Windows的线程本地存储是否初始化值?

Adr*_*thy 5 c++ winapi thread-local-storage

我发现MSDN中有关线程局部存储的初始值的矛盾. 这个页面说:

创建线程时,系统会为TLS分配一个LPVOID值数组,这些值初始化为NULL.

这让我相信,如果我用一个从未为同一索引调用过TlsSetValue的线程调用TlsGetValue,那么我应该得到一个空指针.

但是,这个页面说:

程序员应该确保......在调用TlsGetValue之前调用TlsSetValue.

这表明您不能依赖从TlsGetValue返回的值,除非您确定它已使用TlsSetValue显式初始化.

然而,第二页同时强调了初始化为零的行为:

存储在TLS槽中的数据的值可以为0,因为它仍然具有其初始值,或者因为线程称为TlsSetValue函数为0.

所以我有两个声明说数据被初始化为null(或0),并且有人说我必须在读取值之前显式初始化它.实验上,值似乎是自动初始化为空指针,但我无法知道我是否只是幸运,是否总是这样.

我试图避免使用DLL只是在DLL_THREAD_ATTACH上分配.我想按照以下方式进行懒惰分配:

LPVOID pMyData = ::TlsGetValue(g_index);
if (pMyData == nullptr) {
  pMyData = /* some allocation and initialization*/;
  // bail out if allocation or initialization failed
  ::TlsSetValue(g_index, pMyData);
}
DoSomethingWith(pMyData);
Run Code Online (Sandbox Code Playgroud)

这是一种可靠而安全的模式吗?或者,在尝试阅读之前,是否必须在每个线程中显式初始化插槽?

更新:文档还说TlsAlloc将已分配索引的插槽清空.因此,程序的另一部分之前是否已使用过一个插槽似乎无关紧要.

Ray*_*hen 10

当文档说系统为TLS分配的初始值为零时,文档太有用了.该陈述是真实的,但没有用处.

原因是应用程序可以通过调用释放TLS插槽TlsFree,因此当您分配插槽时,无法保证您是第一个获得该插槽的人.因此,您不知道该值是由系统分配的初始值0还是由前一个插槽所有者分配的其他一些垃圾值.

考虑:

  • 组件A调用TlsAlloc并获得分配的插槽1.插槽1从未使用过,因此它包含其初始值0.
  • 组件A调用TlsSetValue(1, someValue).
  • 组件A调用TlsGetValue(1)someValue返回.
  • 组件A完成并调用TlsFree(1).
  • 组件B调用TlsAlloc并获得分配的插槽1.
  • 组件B调用TlsGetValue(1)someValue返回,因为这是组件A留下的垃圾值.

因此,程序员必须确保线程在调用TlsSetValue之前调用TlsGetValue.否则,你TlsGetValue会读到剩余的垃圾.

误导性文档说"剩余垃圾的默认值为零",但这没有用,因为你不知道在系统初始化它之间和最终给你的时间之间插槽发生了什么.

Adrian的后续跟踪促使我再次研究这种情况,实际上当组件A调用时TlsFree(1),内核将插槽TlsGetValue(1)清零,这样当组件B调用时它就变为零.这假设组件A中没有它TlsSetValue(1)在之后调用的错误TlsFree(1).