我已经使用CUDA流实现了以下类

class CudaStreams
{
    private:
        int             nStreams_;
        cudaStream_t*   streams_;
        cudaStream_t    active_stream_;

    public:

        // default constructor
        CudaStreams() { }

        // streams initialization
        void InitStreams(const int nStreams = 1) {
            nStreams_ = nStreams;
            // allocate and initialize an array of stream handles
            streams_ = (cudaStream_t*) malloc(nStreams_*sizeof(cudaStream_t));
            for(int i = 0; i < nStreams_; i++) CudaSafeCall(cudaStreamCreate(&(streams_[i])));

            active_stream_ = streams_[0];}

        // default destructor
        ~CudaStreams() {
            for(int i = 0; i<nStreams_; i++) CudaSafeCall(cudaStreamDestroy(streams_[i])); }

};

如果我现在运行此简单代码
void main( int argc, char** argv)
{
    streams.InitStreams(1);
    streams.~CudaStreams();

    cudaDeviceReset();
}
cudaDeviceReset()调用之后,我收到以下消息:

test.exe中未处理的异常0x772f15de:0x00000000。

使用cudaDeviceReset()时,应在调用析构函数以避免发生此问题之前该怎么办?

编辑

如果我在析构函数中添加free(streams_);,即
~CudaStreams() {
    for(int i = 0; i<nStreams_; i++) CudaSafeCall(cudaStreamDestroy(streams_[i])); // *
    free(streams_); }

我收到以下错误消息
cudaSafeCall() failed at C:\Users\Documents\Project\Library\CudaStreams.cuh:79 : unknown error

其中79行是析构函数中*表示的行。

此外,如果我直接在代码内使用构造函数和析构函数的相同指令,即
void main( int argc, char** argv)
{
    int nStreams_ = 3;
    cudaStream_t* streams_ = (cudaStream_t*) malloc(nStreams_*sizeof(cudaStream_t));
    for(int i = 0; i < nStreams_; i++) CudaSafeCall(cudaStreamCreate(&(streams_[i])));
    for(int i = 0; i<nStreams_; i++) CudaSafeCall(cudaStreamDestroy(streams_[i]));
    free(streams_);

cudaDeviceReset();
}

一切正常。偷窥与某类的不良使用有关吗?

最佳答案

这里有两个问题,都与您的类和作用域的析构函数有关。

首先,让我们从可以正常运行的main()版本开始:

int main( int argc, char** argv)
{
    {
        CudaStreams streams;
        streams.InitStreams(1);
    }

    cudaDeviceReset();

    return 0;
}

这是正确的,因为streams的析构函数只被调用一次(当streams超出范围时),并且在cudaDeviceReset被调用之前。

您的原始main()(或它的可编译版本,但稍后会详细介绍...)由于两个原因而失败。让我们再来看一看:
int main( int argc, char** argv)
{
    CudaStreams streams;
    streams.InitStreams(1);
    streams.~CudaStreams();

    cudaDeviceReset();

    return 0;
}

在这里,您明确地调用了streams的析构函数(您几乎永远不会这样做),然后是cudaDeviceReset,然后在streams超出范围时在return语句中再次调用该析构函数。上下文被破坏后自动调用析构函数是segfault / exception的来源。 cudaStreamDestroy调用尝试在没有有效CUDA上下文的情况下在流上工作。因此,解决方案是在没有上下文的情况下,没有任何使CUDA API调用超出范围(或显式调用其析构函数)的类。

如果我们制作了第三个这样的版本:
int main( int argc, char** argv)
{
    {
        CudaStreams streams;
        streams.InitStreams(1);
        streams.~CudaStreams();
    }

    cudaDeviceReset();

    return 0;
}

您将收到CUDA运行时错误。因为析构函数被调用两次。第一次(明确)它将起作用。第二个(隐式,超出范围)将产生运行时错误:您具有有效的上下文,但是现在正尝试销毁不存在的流。

作为最后的评论/问题:发布和显示原始问题中显示的代码的实际可编译版本有多困难?它实际上需要5条额外的行才能使其成为其他人可以实际编译并运行的适当复制实例。如果您不愿意付出类似的努力来提供有用的代码和信息,而这又使每个人的生活变得更加轻松,那么我期望别人努力回答基本上是调试问题的想法有点不合理。想一想。 [结束语]

09-26 12:29