【问题标题】:Function in consumer thread unable to access memory location消费者线程中的函数无法访问内存位置
【发布时间】:2017-09-12 20:55:55
【问题描述】:

我有一些处理图像的代码。性能至关重要,所以我很想使用BoundedBuffer 实现多线程。图像数据存储为unsigned char*(由我用来处理图像数据的SDK决定)。

问题出现在消费者线程中调用的processData函数中。在processData 内部,还有另一个使用cudaMemcpy2D 函数的函数(来自图像处理SDK)。 cuda 函数总是抛出一个异常说访问冲突读取位置。

但是,如果我直接在生产者线程中调用processDatadeposit,cuda 函数可以正常工作。当我从消费者线程(根据需要)调用processData 时,我从 cuda 函数中得到了异常。我什至尝试从 fetch 中调用 processData,但我得到了同样的异常。

我的猜测是,在数据被生产者线程deposited 到rawImageBuffer 之后,unsigned char* 指向的内存不知何故发生了变化,因此消费者线程(或fetch)实际上发送了错误的图像数据到processData(和cuda函数)。

这是我的代码的样子:

void processData(vector<unsigned char*> unProcessedData)
{
    // Process the data
}

struct BoundedBuffer {
    queue<vector<unsigned char*>> buffer;
    int capacity;

    std::mutex lock;

    std::condition_variable not_full;
    std::condition_variable not_empty;

    BoundedBuffer(int capacity) : capacity(capacity) {}

    void deposit(vector<unsigned char*> vData) 
    {
        std::unique_lock<std::mutex> l(lock);

        bool bWait = not_full.wait_for(l, 3000ms, [this] {return buffer.size() != capacity; }); // Wait if full

        if (bWait)
        {
            buffer.push(vData); // only push data when timeout doesn't expire
            not_empty.notify_one();
        }           
    }

    vector<unsigned char*> fetch()
    {
        std::unique_lock<std::mutex> l(lock);

        not_empty.wait(l, [this]() {return buffer.size() != 0; }); // Wait if empty

        vector<unsigned char*> result{};

        result = buffer.front();
        buffer.pop();

        not_full.notify_one();

        return result;
    }
};

void producerTask(BoundedBuffer &rawImageBuffer)
{
    for(;;)
    {
        // Produce Data
        vector<unsigned char*> producedDataVec{dataElement0, dataElement1};
        rawImageBuffer.deposit(producedDataVec);
    } //loop breaks upon user interception
}

void consumerTask(BoundedBuffer &rawImageBuffer)
{
    for(;;)
    {
        vector<unsigned char*> fetchedDataVec{};
        fetchedDataVec = rawImageBuffer.fetch();
        processData(fetchedDataVec);
    } //loop breaks upon user interception 
}

int main()
{
        BoundedBuffer rawImageBuffer(6);

        thread consumer(consumerTask, ref(rawImageBuffer));
        thread producer(producerTask, ref(rawImageBuffer), 

        consumer.join();
        producer.join();

        return 0;
}

我对抛出异常的原因的猜测是否正确?我该如何解决这个问题?作为参考,每个向量元素都包含 RGBa 8 位格式的 2448px X 2048px 图像的数据。

更新:

  1. 在cmets中有人指出unsigned char*指针可能无效后,我发现指针指向的地址实际上是一个真实的内存位置。异常中访问冲突读取位置 X.X大于指针指向的位置。

  2. 经过一番调试,我发现unprocessedData中的unsigned char*指向的内存在processData中并没有保持原样,指针地址是正确的,但是有些块内存不可读。我通过在unsigned char*processData 中打印每个char 发现了这一点。当生产者线程调用processData 时(这是cuda 不抛出异常的时候),所有chars 都可以很好地打印出来(我打印的是2048*2448*4 chars,由上述图像分辨率决定和格式)。但是当processData 被消费者线程调用时,打印char 会抛出相同的异常,在第40 个char 左右(在第40 个左右,并不总是在第40 个左右)抛出异常。

  3. 1234563对此进行了测试。为了测试这一点,在producerTask 中,我特意将测试值(例如int 42,或char *)写入unsigned char* 指向的0th 内存块。在processData 函数中,我检查内存块是否仍然包含测试值,并且确实如此。所以,现在我知道指针指向的一些内存块由于某种未知原因变得无法读取。此外,我的测试并不能证明第一个内存块不会变得不可访问,只是在我进行的少数测试中它并没有变得不可访问。 更新 1 到 3 的 TLDRunprocessedImage 指针是有效的,它们指向一个真实的内存地址,它们也指向保存预期值的内存地址。
  4. 又一次调试尝试。现在我正在使用 Visual Studio 的内存窗口来直观地检查数据。调试器告诉我unProcessedData[0] 指向0x00000279d7c76070。这是0x00000279d7c76070 周围的内存的样子: 记忆似乎是明智的,RGBa 格式可以清楚地看到,图像全黑,所以RGB 通道接近 0 而alphaff 是有道理的。我向下滚动了很长时间以查看内存的样子,一直到0x00000279D8F9606F 数据看起来不错(RGBa 值符合预期)。 0x00000279D8F9606F 数字也很有意义,因为 0x00000279D8F9606F - 0x00000279d7c76070 = 0d20054015,这意味着有 20054016 个有效 chars 是预期的(2048 高度*2448 宽度*4 通道 = 20054016)。好的,到目前为止一切顺利。请注意,所有这些都在运行 cuda 函数之前。单步执行 cuda 函数后,我得到了同样的异常:访问冲突读取位置0x00000279D80B8000。请注意,0x00000279D80B8000 介于 0x00000279d7c760700x00000279D8F9606F 之间,这是我目测正确的内存部分。现在,运行 cuda 函数后,0x00000279d7c760700x00000279D8F9606F 之间的内存如下所示:

  5. 当我在调用 cuda 函数之前 cout processData 中的任何内容时。指针指向的内存发生变化。所有chars 都等同于0xdd,如下图所示。 This MSDN 上的页面说 The freed blocks kept unused in the debug heap's linked list when the _CRTDBG_DELAY_FREE_MEM_DF flag is set are currently filled with 0xDD. 但是当我从生产者线程调用processData 时,在我cout 之后,指向的内存不会改变。

目前对这个问题最受好评的评论是告诉我更多地了解指针。我目前正在这样做(希望我的更新可能会暗示),但是我需要了解哪些主题?我确实知道指针是如何工作的。我知道我的指针指向有效的内存位置(请参阅更新 2)。我知道指针指向的一些内存块变得无法读取(参见更新 3)。但我不知道为什么内存块变得不可访问。特别是,我不知道为什么它们只有在从消费者线程调用processData 时才变得不可访问(请注意,当从生产者线程调用processData 时不会抛出异常)。我还能做些什么来帮助缩小这个问题的范围吗?

【问题讨论】:

  • 如果“性能至关重要”,那么所有这些参数按值传递的目的是什么,浪费时间制作完全冗余的多个数据副本?
  • 这就是unsigned char *s 所指向的数据吗?有什么可以确保这些指针指向的内容是有效的,并且在processData() 处理它时还没有被释放。
  • 那么,在尝试处理多线程等高级主题之前,您可能需要花一些时间获得一些“经验”并了解指针的工作原理。这就像在没有很好地掌握固体燃料火箭助推器的工作原理的情况下试图将火箭发射到太空中一样。如果您不完全理解这些指针指向的内容,那里的数据是否实际有效,那么当某个线程查看它时,我想不出任何其他建议。
  • 您可能会将切片放在地板上,因为您的等待时间 2000 和 3000 不同。也许这会破坏数据。 wait_for 返回一个您可以使用的布尔值。 (如果你没有添加矢量,你也不需要调用 notify。)事实上,也许你需要一个循环来确保添加了一些东西......删除一张图片是否安全?跨度>
  • 你为什么认为发布的任何代码都与问题有关?你从一个未指定的地方(dataElement0,dataElement1)得到一些char*s并将它们传递(在线程安全缓冲区缓冲区中队列中的向量中,但不清楚为什么这很重要)以及何时阅读它不存在的数据。您应该找出 (1) 哪些组件拥有 数据(负责删除数据)(2) 何时发生此删除 (3) 是否存在缓冲区溢出或其他可能损坏数据的 UB数据。

标签: c++ multithreading pointers producer-consumer


【解决方案1】:

问题很简单,n.m. 的 cmets 引导我朝着正确的方向前进,我对此表示感谢。

在我的更新中,我提到使用cout 打印任何内容都会导致数据损坏。虽然,这似乎正在发生,但在 fetchdeposit 中设置了一些断点后,我对真正发生的事情有了一个完整的了解。

我生成图像数据的方式是使用相机随附的另一个 SDK,该 SDK 为我提供了包装指针类型的图像数据。然后我转换了图像格式,然后解开转换后的图像以获取指向原始图像的指针。然后指向原始图像的指针存储到producedDataVecdeposit 将它存储到rawImageBuffer。问题是,一旦转换后的图像超出范围,我的数据就会损坏。因此,cout 语句并没有真正对破坏我的数据负责。到处都有断点,我可以看到在转换后的图像超出范围后数据变得损坏。为了解决这个问题,现在我的生产者直接deposits 包装了指向缓冲区的指针。消费者fetches包装后的指针,通过消费者中的格式转换得到转换后的图像,然后得到原始图像指针。现在转换后的图像仅在processData 返回后才超出范围,因此永远不会抛出异常。

【讨论】:

  • 把这个旁注放在这里作为评论:我的问题的一些 cmets 不仅居高临下,而且还把我引向了错误的方向。我知道我不像这里的大多数用户那样了解 SO,但最终此类 cmets 是有害的,因为这些 cmets 会导致其他用户避免回答问题。
猜你喜欢
  • 2023-02-07
  • 2021-12-19
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-12
  • 1970-01-01
相关资源
最近更新 更多