在动态DLL中使用线程私有变量?

时间:2012-10-02 03:29:36

标签: c multithreading visual-studio-2010 openmp

我正在寻找有关OpenMP的一些建议。我对使用threadprivate变量感到困惑。问题可以在以下示例中显示:

-----------

// 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)。

Microsoft说http://msdn.microsoft.com/en-us/library/2z1788dd.aspx“您不能在任何将通过LoadLibrary加载的DLL中使用threadprivate。这包括使用/ DELAYLOAD(延迟加载导入)加载的DLL,它也使用LoadLibrary。“因此,如果我说得对,上面的代码不正确 - threadprivate变量和动态加载的DLL不会混合。

为了验证这一点,我创建了一个动态加载DLL的测试项目,使用threadprivate并行运行一个函数,如上所述。这一切都很好!

嗯......现在我感到很困惑,因为该项目不应该起作用。

我真的可以在dinamic DLL中使用线程私有变量,还是有诀窍呢?

谢谢,

亚历

3 个答案:

答案 0 :(得分:1)

MS OpenMP实现中的

threadprivate被转换为__declspec(thread),它将声明的变量放在静态TLS(线程局部存储)中。启动程序时,通过考虑可执行文件所需的TLS大小以及所有其他隐式加载的DLL的TLS要求来确定TLS的大小。当您使用LoadLibrary动态加载另一个DLL或使用FreeLibrary卸载它时,系统必须检查所有正在运行的线程并相应地放大或压缩它们的TLS存储。根据{{​​3}}:

  

此过程对于操作系统来说太多了,这可能会在动态加载DLL或代码引用数据时导致异常。

访问此类变量被视为未定义的行为。它适用于您的情况,但并不意味着它可以随处运行。 KB118816您可以在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(错误检查)。

答案 1 :(得分:0)

我没有尝试过OMP,但我有很好的使用经验

DWORD WINAPI TlsAlloc(void);

和其他TlsXxx函数。我很确定threadprivate(p)是一个最终来到这个函数的包装器。此函数在exe,静态和动态加载的dll等中完美运行。

在一个案例中,我在我的流程中同时看到了大约800个TLS索引。每个线程(大约200个线程)在其线程本地存储中具有此数量的对象。 NT在堆中分配缓冲区来处理这些数据。这一切都很好。

可能是在撰写MSDN文章时,存在一些问题,但很可能现在已经修复了。

我的2分。

答案 2 :(得分:0)

Hristo和Kirill,

感谢您的回复。

似乎Microsoft已在动态DLL中解决了TLS问题(至少在我使用的Windows 7中)。也许他们只是没有更新文档。我保持手指交叉。

我创建了一个测试项目,我通过__declspec(thread)编译指示使用TLS测试了这个想法(正如Hristo Iliev在另一篇文章中向我建议的那样)。该项目使用了大量的TLS变量(仅仅是为了测试)而且一切正常。然后我移动了工作项目中的代码。这是一个大型项目(约100万行代码),可以加载许多动态DLL。这一切都在那里工作。

我仍然坚持使用TlsAlloc,TlsSetValue,TlsGetValue函数来处理更糟糕的情况。但它并不适合我,因为我担心这些功能会增加对我所拥有的数字运算代码的重大监听。我的库只需要一些TLS变量,但这些变量通过代码广泛使用,包括位于堆栈深处的低级函数。

干杯,

Alex Yamchikov