繁体   English   中英

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

[英]Does Windows's thread-local storage initialize values?

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

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

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

但是, 这个页面说:

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

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

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

存储在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);

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

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

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

原因是应用程序可以通过调用 TlsFree释放TLS插槽,因此当您分配插槽时,无法保证您是第一个获得该插槽的人。 因此,您不知道该值是由系统分配的初始值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) ,内核确实将插槽TlsFree(1) ,这样当组件B调用TlsGetValue(1)它就会变为零。 这假设组件A中没有错误,它在TlsSetValue(1)之后TlsFree(1)

Raymond Chen的推理很有道理,所以我昨天接受了他的回答,但今天我在TlsAlloc的MSDN文档中发现了另一个关键路线:

如果函数成功,则返回值为TLS索引。 索引的槽初始化为零。

如果TlsAlloc确实将插槽初始化为零,那么您不必担心该插槽的前一个用户留下的垃圾值。 为了验证TlsAlloc确实以这种方式运行,我运行了以下实验:

void TlsExperiment() {
  DWORD index1 = ::TlsAlloc();
  assert(index1 != TLS_OUT_OF_INDEXES);
  LPVOID value1 = ::TlsGetValue(index1);
  assert(value1 == 0);  // Nobody else has used this slot yet.
  value1 = reinterpret_cast<LPVOID>(0x1234ABCD);
  ::TlsSetValue(index1, value1);
  assert(value1 == ::TlsGetValue(index1));
  ::TlsFree(index1);

  DWORD index2 = ::TlsAlloc();
  // There's nothing that requires TlsAlloc to give us back the recently freed slot,
  // but it just so happens that it does, which is convenient for our experiment.
  assert(index2 == index1); // If this assertion fails, the experiment is invalid.

  LPVOID value2 = ::TlsGetValue(index2);
  assert(value2 == 0);  // If the TlsAlloc documentation is right, value2 == 0.
                        // If it's wrong, you'd expect value2 == 0x1234ABCD.
}

我在Windows 7上运行了实验,包括使用VS 2010编译的32位和64位测试程序。

结果支持TlsAlloc将值重新初始化为0的想法。我认为TlsAlloc可能正在做一些蹩脚的事情,比如将当前线程的值归零,但文档明确地说“槽”(复数),所以它似乎是安全的假设如果您的线程尚未使用插槽,则该值将为0。

更新:进一步的实验表明,TlsFree而不是TlsAlloc将插槽重新初始化为0.因此,正如Raymond指出的那样, 释放插槽 ,仍然存在另一个组件称为TlsSetValue的风险。 这将是另一个组件中的错误,但它会影响您的代码。

我没有看到矛盾。 第二页只是告诉您系统将所有TLS插槽初始化为0但超出一些基本边界检查,无法知道特定TLS索引是否包含有效数据( 0可能是完全有效的用户设置)值也是!)并且由您来确保您请求的索引是您想要的索引并且它包含有效数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM