3 回答
threadprivate
在 MS OpenMP 实现中被转换为__declspec(thread)
将声明的变量放入静态 TLS(线程本地存储)中。当程序启动时,TLS 的大小是通过考虑可执行文件所需的 TLS 大小以及所有其他隐式加载的 DLL 的 TLS 要求来确定的。当您使用 动态加载LoadLibrary
或卸载另一个 DLL 时FreeLibrary
,系统必须检查所有正在运行的线程并相应地扩大或压缩它们的 TLS 存储。根据KB118816:
这个过程对于操作系统来说管理太多,当动态加载 DLL 或代码引用数据时,这可能会导致异常。
访问这些变量被认为是未定义的行为。它适用于您的情况,但这并不意味着它可以随时随地使用。在这里,您可以了解为什么它在 Windows XP/2003 和更早的 Windows 版本上很可能会失败。根据同一来源,隐式 TLS 处理在 Windows Vista 和 OpenMP 中被重写,并且threadprivate
从那时__declspec(thread)
起应该在运行时加载的 DLL 中正确运行。建议的解决方案是改为使用TlsAlloc
。
DWORD dwTlsIdx;
extern "C" __declspec(dllexport) int MyFunc1(void)
{
int i;
#pragma omp parallel for
for (i = 0; i < n; i++) {
MyFunc2(i);
}
return TRUE;
}
void MyFunc2(void)
{
int **pp = (int **)TlsGetValue(dwTlsIdx);
*pp = malloc(sizeof(int));
**pp = 0;
printf(“value = %d”,**pp);
free(*pp);
}
dwTlsIdx
应通过DllMain
调用TlsAlloc
. 应该在线程附加上分配足够的内存来保存一个int *
,并且它的地址应该设置为dwTlsIdx
TLS 索引的值。或者你可以在第一次调用时这样做MyFunc2
:
void MyFunc2(void)
{
int **pp = (int **)TlsGetValue(dwTlsIdx);
if (pp == NULL)
{
pp = malloc(sizeof(int *));
TlsSetValue(dwTlsIdx, pp);
}
*pp = malloc(sizeof(int));
**pp = 0;
printf(“value = %d”,**pp);
free(*pp);
}
有关更多详细信息,请参见此处(带有错误检查)。
我没有尝试使用 OMP,但我有很好的使用经验
DWORD WINAPI TlsAlloc(void);
和其他TlsXxx
功能。我很确定这threadprivate(p)
是最终实现此功能的包装器。此功能在 exe、静态和动态加载的 dll 等中完美运行。
在一种情况下,我在处理过程中同时看到了大约 800 个 TLS 索引。每个线程(大约 200 个线程)在其线程本地存储中都有这个数量的对象。NT 在堆中分配缓冲区来处理这些数据。所有这一切都很好。
可能是在写 MSDN 文章的时候出现了一些问题,但现在很可能已经修复了。
我的 2 美分。
赫里斯托和基里尔,
感谢您的回复。
微软似乎已经解决了动态 DLL 中的 TLS 问题(至少在我使用的 Windows 7 中)。也许他们只是没有更新文档。我保持手指交叉。
我创建了一个测试项目,在其中通过 __declspec(thread) pragmas 使用 TLS 测试了这个想法(正如 Hristo Iliev 在另一篇文章中向我建议的那样)。该项目使用了大量的 TLS 变量(只是为了测试)并且一切正常。然后我在工作项目中移动了代码。这是一个加载许多动态 DLL 的大型项目(约 100 万行代码)。这一切也都在那里工作。
对于最坏的情况,我仍然持有使用 TlsAlloc、TlsSetValue、TlsGetValue 函数的想法。但它并不适合我,因为我担心这些功能会在我拥有的数字运算代码中增加大量偷听。我的库只需要一些 TLS 变量,但这些变量在代码中被广泛使用,包括位于堆栈深处的低级函数。
干杯,
亚历克斯·亚姆奇科夫