OMP线程专有对象未被销毁

4

底线

如何确保threadprivate实例被正确销毁?

背景

当回答this question时,我遇到了一个奇怪的问题,使用Intel C++ 15.0编译器在VS2013中声明全局变量threadprivate时,从属线程的副本没有被销毁。我开始寻找强制销毁它们的方法。在this网站上,他们说添加OMP屏障应该有所帮助。但并没有(请参见MCVE)。我尝试将OMP块时间设置为0,以便线程不会在并行区域之后停留(也没有帮助)。我尝试添加一些虚拟计算来延迟主线程,使其他线程有时间死亡。仍然没有帮助。

MCVE:

#include <iostream>
#include <omp.h>

class myclass {
    int _n;
public:
    myclass(int n) : _n(n) { std::cout << "int c'tor\n"; }

    myclass() : _n(0) { std::cout << "def c'tor\n"; }

    myclass(const myclass & other) : _n(other._n)
                    { std::cout << "copy c'tor\n"; }

    ~myclass() { std::cout << "bye bye\n"; }

    void print() { std::cout << _n << "\n"; }

    void add(int t) { _n += t; }
};

myclass globalClass;
#pragma omp threadprivate (globalClass)

int main(int argc, char* argv[])
{
    std::cout << "\nBegninning main()\n";

    // Kill the threads immediately
    kmp_set_blocktime(0);

#pragma omp parallel
    {
        globalClass.add(omp_get_thread_num());
        globalClass.print();
#pragma omp barrier
        //Barrier doesn't help
    }

    // Try some busy work, takes a few seconds
    double dummy = 0.0;
    for (int i = 0; i < 199999999; i++)
    {
        dummy += (sin(i + 0.1));
    }
    std::cout << dummy << "\n";
    
    std::cout << "Exiting main()\n";
    return 0;
}

输出结果为:

构造函数
进入main()
构造函数
1
构造函数
3
构造函数
2
0
1.78691
退出main()
再见

我预期会有四个“再见”,但只有一个。

更新

根据Kyle引用的OMP 4.0标准,其中指出:

所有线程专用变量的所有副本的存储将按照基本语言中静态变量的处理方式进行释放,但在程序的某个未指定点进行。

我添加了该类的静态实例(全局和局部),以查看其析构函数是否被调用。它确实被调用,无论是对于局部还是全局情况。因此问题仍然存在。

1个回答

3

这是已记录的行为(尽管我不知道为什么会做出这个决定)。

来自 MSDN 关于 threadprivate 的条目(稍作格式更改):

可销毁类型的 threadprivate 变量不能保证其析构函数被调用。

...

用户无法控制 并行区域中的线程何时终止。如果这些线程在进程退出时存在,那么这些线程将不会收到关于进程退出的通知,并且除了退出的线程(即主线程)之外,其他任何线程都不会调用 threaded_var 的析构函数。因此,代码不应该指望正确销毁 threadprivate 变量。

OpenMP 4.0 版本标准 没有指定析构函数调用行为的 顺序。从第 12.14.2 节:

第151页,第7-9行:

所有线程私有变量的副本存储将按照基础语言中静态变量的处理方式进行释放,但在程序的某个未指定的点进行。

第152页,第8-10行:

对于不同类类型的任何线程私有变量,其构造函数调用顺序是未指定的。对于不同类类型的任何线程私有C ++变量,其析构函数调用顺序也是未指定的。

个人认为,微软可能将此视为太多的空白支票;析构函数的顺序未指定似乎与根本未保证调用析构函数有很大的区别。在基础语言(在这种情况下为C ++)中处理静态变量的方式是保证调用析构函数。因此,我认为MSVC不符合标准(C ++标准和OMP标准),但由于我不是语言律师,请勿轻信我的话。

话虽如此,很难看到这会有严重的后果。你肯定不应该看到任何内存泄漏,因为threadprivate存储空间应该在线程创建/销毁时一次性分配/释放。(如果你的threadprivate实例引用了它们管理的非threadprivate内存,那么...这似乎一开始就行不通。)


我看过了。有点好笑,因为VS不会编译我的示例。这是OpenMP标准的一部分吗?我在Intel编译器中没有找到类似的东西(唯一一个我找到/拥有的可以工作的编译器)。 - Avi Ginsburg
@AviGinsburg 我刚查了一下,在第四版中它是未指定的。 - Kyle Strand
我已经添加了参考。 - Kyle Strand
从第181页开始: “所有线程专用变量的副本存储将根据基础语言中静态变量的处理方式进行释放,但在程序的某个未指定的点进行。” 第182页: “调用不同的C ++类类型的任何线程专用变量的析构函数的顺序是未指定的。” - Avi Ginsburg
你添加了相同的信息...我同意你的理解,order似乎意味着它们将被调用。但这应该是一个无关紧要的问题,因为编译器是Intel C++而不是cl。 - Avi Ginsburg
@AviGinsburg 哦,你的第一条引用(我错过了)也很相关,所以我也加上了。 - Kyle Strand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接