在动态 DLL 中使用线程私有变量?
Posted
技术标签:
【中文标题】在动态 DLL 中使用线程私有变量?【英文标题】:Using thread private variables in dynamic DLLs? 【发布时间】:2012-10-02 03:29:36 【问题描述】:我正在寻找有关 OpenMP 的一些建议。我对线程私有变量的使用感到困惑。问题可以用下面的例子来表示:
-----------
// The code below is located the dynamically loaded DLL.
/* Global variable. */
int *p;
#pragma omp threadprivate(p)
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)
p = malloc(sizeof(int));
*p = 0;
printf(“value = %d”,*p);
free(p);
-----------
在这里,我希望每个线程都有一个全局线程独立变量的单独副本,该变量将在线程的所有函数中可见。该变量将在线程内被初始化和销毁。
这里的“问题”是所有代码包括全局变量“p”的定义都位于动态加载的DLL(通过LoadLibrary)。
微软说http://msdn.microsoft.com/en-us/library/2z1788dd.aspx:“你不能在任何将通过 LoadLibrary 加载的 DLL 中使用 threadprivate。这包括使用 /DELAYLOAD(延迟加载导入)加载的 DLL,它也使用 LoadLibrary。” 所以,如果我没看错的话,上面的代码是不正确的——线程私有变量和动态加载的 DLL 不能混合使用。
为了验证这一点,我创建了一个动态加载 DLL 的测试项目,添加运行一个函数并行使用 threadprivate,如上所述。一切都很好!
嗯......现在我很困惑,因为那个项目不应该工作。
我真的可以在动态 DLL 中使用线程私有变量吗?或者有什么诀窍吗?
谢谢,
亚历克斯
【问题讨论】:
它没有正常工作。你预计它会失败,它并没有失败。不做你期望的事情永远不应该被描述为工作得很好。你违反了规则,它没有达到你的预期。 这就是你一开始就应该预料到的。 【参考方案1】:MS OpenMP 实现中的threadprivate
被转换为__declspec(thread)
,它将声明的变量放入静态 TLS(线程本地存储)中。当程序启动时,TLS 的大小是通过考虑可执行文件所需的 TLS 大小以及所有其他隐式加载的 DLL 的 TLS 要求来确定的。当您使用LoadLibrary
动态加载另一个DLL 或使用FreeLibrary
卸载它时,系统必须检查所有正在运行的线程并相应地扩大或压缩它们的TLS 存储。根据KB118816:
这个过程对于操作系统来说管理太多,当动态加载 DLL 或代码引用数据时可能会导致异常。
对此类变量的访问被视为未定义行为。它适用于您的情况,但这并不意味着它可以随时随地使用。 Here 您可以阅读为什么它在 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);
请参阅here 了解更多详情(带错误检查)。
【讨论】:
我在提到的文章中没有看到与“如果没有足够的资源,功能可能会失败”有什么不同。他们没有指出任何具体问题。他们没有提到任何最近的 Windows 版本。您答案中的代码示例仍将数据存储在同一个堆中。您对malloc
的调用很容易以完全相同的方式失败。【参考方案2】:
我没有尝试使用 OMP,但我有很好的使用经验
DWORD WINAPI TlsAlloc(void);
和其他TlsXxx
函数。我很确定threadprivate(p)
是最终实现此功能的包装器。此功能在 exe、静态和动态加载的 dll 等中完美运行。
在一种情况下,我在处理过程中同时看到大约 800 个 TLS 索引。每个线程(大约 200 个线程)在其线程本地存储中都有这个数量的对象。 NT 在堆中分配缓冲区来处理这些数据。这一切都很好。
可能是在写 MSDN 文章的时候出现了一些问题,但现在很可能已经修复了。
我的 2 美分。
【讨论】:
显然它在 Windows XP 上仍然会导致问题 - 请参阅 this ticket。【参考方案3】:赫里斯托和基里尔,
感谢您的回复。
看来微软已经解决了动态 DLL 中的 TLS 问题(至少在我使用的 Windows 7 中)。也许他们只是没有更新文档。我一直在祈祷。
我创建了一个测试项目,在其中通过 __declspec(thread) pragmas 使用 TLS 测试了这个想法(正如 Hristo Iliev 在另一篇文章中向我建议的那样)。该项目使用了大量的 TLS 变量(只是为了测试)并且一切正常。然后我在工作项目中移动了代码。这是一个加载许多动态 DLL 的大型项目(约 100 万行代码)。这一切也都在那里工作。
对于最坏的情况,我仍然坚持使用 TlsAlloc、TlsSetValue、TlsGetValue 函数的想法。但它不适合我,因为我担心这些功能会给我拥有的数字运算代码增加大量偷听。我的库只需要一些 TLS 变量,但这些变量在代码中被广泛使用,包括位于堆栈深处的低级函数。
干杯,
亚历克斯·亚姆奇科夫
【讨论】:
您可以在并行区域的开头获取指向 TLS 区域的指针,并将其向下传递到调用堆栈给使用线程私有变量的函数。隐式 TLS 访问也是有代价的 - 64 位模式下的 4 个MOV
指令实现了 TlsGetValue(_tls_index)
的精简内联版本。以上是关于在动态 DLL 中使用线程私有变量?的主要内容,如果未能解决你的问题,请参考以下文章