C++ 析构函数中的cuda调用失败

C++ 析构函数中的cuda调用失败,c++,stl,cuda,singleton,C++,Stl,Cuda,Singleton,我是CUDA新手,在使用CUDA编写单例/全局变量时遇到了一个问题。singleton分配一些cuda内存,并尝试在析构函数中释放它。但是,析构函数崩溃时,cudaError为29“驱动程序关闭” 通过一些搜索,我注意到原因可能是当CUDA已经关闭时,程序退出后调用了单例析构函数 当在静态成员的析构函数中调用cuda函数时,此链接会报告类似的问题 此链接报告了相同的问题和不清楚的解决方案 老实说,我没有太多的CUDA知识,所以我想问一些详细的解释和正式的解决方案,这个问题 编辑: 感谢@Ro

我是CUDA新手,在使用CUDA编写单例/全局变量时遇到了一个问题。singleton分配一些cuda内存,并尝试在析构函数中释放它。但是,析构函数崩溃时,cudaError为29“驱动程序关闭”

通过一些搜索,我注意到原因可能是当CUDA已经关闭时,程序退出后调用了单例析构函数

当在静态成员的析构函数中调用cuda函数时,此链接会报告类似的问题

此链接报告了相同的问题和不清楚的解决方案

老实说,我没有太多的CUDA知识,所以我想问一些详细的解释和正式的解决方案,这个问题

编辑:

感谢@Robert Crovella的提醒,我做了一些测试来重现这个问题。好的,我发现这个问题发生在std::unordered_映射或std::map的单例变量和全局变量中,后者在其值对象的析构函数中调用cuda

工作代码,未使用std::map:

#include <iostream>
#include <map>

#define CUDA_CHECK(x) std::cerr << (x) << std::endl;

class cuda_user
{   
    char* data;
public:
    cuda_user() {
        std::cerr << "constr" << std::endl;
        CUDA_CHECK(cudaMalloc((void**)&data, 1024));
    }
    void foo() {
        std::cerr << "foo" << std::endl;
    };
    ~cuda_user() {
        std::cerr << "destr"  << std::endl;
        CUDA_CHECK(cudaFree(data));
    }
};

cuda_user cu;
int main()
{   
    cu.foo();
}
崩溃代码,具有相同的cuda_用户类别,但使用了std::map:

#include <iostream>
#include <map>

#define CUDA_CHECK(x) std::cerr << (x) << std::endl;

class cuda_user
{   
    char* data;
public:
    cuda_user() {
        std::cerr << "constr" << std::endl;
        CUDA_CHECK(cudaMalloc((void**)&data, 1024));
    }
    void foo() {
        std::cerr << "foo" << std::endl;
    };
    ~cuda_user() {
        std::cerr << "destr"  << std::endl;
        CUDA_CHECK(cudaFree(data));
    }
};

std::map<int, cuda_user> map;
int main()
{   
    map[1].foo();
}
#包括
#包括

#定义CUDA_检查(x)标准::cerr[将注释扩展为摘要答案]

您的代码在不知不觉中依赖于未定义的行为(翻译单元对象的销毁顺序),除了在其析构函数中显式控制包含CUDA运行时API调用的对象及其生命周期,或者干脆避免在析构函数中使用这些API调用之外,没有真正的解决方法

详细内容:

nvcc调用的CUDA前端以静默方式添加了大量样板代码和翻译单元范围对象,这些对象执行CUDA上下文设置和拆卸。在执行任何依赖CUDA上下文的API调用之前,必须先运行该代码。如果在其析构函数中包含CUDA运行时API调用的对象在拆除上下文后调用该API,则代码可能会因运行时错误而失败。当对象脱离范围时,C++不定义销毁顺序。在拆除CUDA上下文之前,需要销毁单例或对象,但不能保证会发生这种情况。这实际上是未定义的行为


在这里,您可以看到一个更完整的示例(在内核启动的上下文中)。

在离开main之前清理您的单例:
int main(){{{SingletonController singcont;/*..*..*/}返回0;}
如果您有线程,您还应该确保它们在离开main中的内部作用域块之前结束。然后,
SingletonController
的desctructor将不会与其他线程争用,您将完全关闭。当然,大多数做单例的人都不是这样做的……他们对CUDA一无所知,但在操作系统消除进程并释放所有内存之前的几毫秒内,您需要释放内存,这有什么特别的原因吗?我感觉到一种对“内存泄漏”的毫无根据的恐惧,但也许CUDA的情况有所不同。单身汉是出了名的狡猾,至少有。你能举一个简单的例子说明你在用什么吗?我怀疑您的具体实施可能会影响@BitTickler建议的方法的可行性。如果您不打算使用“可清理”的单例设计模式,那么Christian Hackl建议的方法可能是最简单的:从析构函数中消除cuda调用。将cuda调用放置在
main
范围之外的全局对象中会导致有问题的行为。看见尽管该描述主要关注此类类/对象中的内核调用,但正如您所发现的,危险适用于任何CUDA调用。我认为你的问题可以说是这个问题的重复,至少在你要求描述正在发生的事情时是这样,现在你的例子没有任何单例字符。@BoLi:是的,很抱歉术语错误。当您使用CUDA运行时API时,CUDA前端会无声地发出大量样板支持代码,这些代码的作用域与翻译单元作用域映射/单例/对象的作用域相同。您的代码是否能正常工作取决于对象实例化和销毁的特定序列是否意味着当您的类调用包含运行时API调用的构造函数/析构函数时,仍然存在活动CUDA上下文。语言中没有定义对象实例化和销毁的顺序——这实际上依赖于UB。
#include <iostream>
#include <map>

#define CUDA_CHECK(x) std::cerr << (x) << std::endl;

class cuda_user
{   
    char* data;
public:
    cuda_user() {
        std::cerr << "constr" << std::endl;
        CUDA_CHECK(cudaMalloc((void**)&data, 1024));
    }
    void foo() {
        std::cerr << "foo" << std::endl;
    };
    ~cuda_user() {
        std::cerr << "destr"  << std::endl;
        CUDA_CHECK(cudaFree(data));
    }
};

std::map<int, cuda_user> map;
int main()
{   
    map[1].foo();
}
constr
0
foo
destr
29 << Error!