我有一些处理图片的代码。性能至关重要,因此我要使用BoundedBuffer
实现多线程。图像数据存储为unsigned char*
(由我用来处理图像数据的SDK决定)。
问题出现在使用者线程中调用的processData
函数中。在processData
内,还有另一个使用cudaMemcpy2D
函数的函数(来自图像处理SDK)。 cuda函数总是抛出异常,说明访问冲突读取位置。
但是,如果我直接在生产者线程或processData
内调用deposit
,则cuda函数可以正常工作。当我从消费者线程(根据需要)调用processData
时,我从cuda函数中获取异常。我甚至尝试从fetch中调用processData
,我得到了相同的异常。
我的猜测是,在生产者线程将数据deposit
编入rawImageBuffer
后,unsigned char*
指向的内存会以某种方式更改,从而消费者线程(或{{ 1}})实际上将错误的图像数据发送到fetch
(以及cuda函数)。
这就是我的代码:
processData
我猜错了为什么会抛出异常?我该如何解决这个问题?作为参考,每个矢量元素包含RGBa 8bit格式的2448px X 2048px图像的数据。
更新
有人在评论中指出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;
}
指针可能无效,我发现指针指向的地址实际上是一个真实的内存位置。在异常访问冲突读取位置 X.X大于指针指向的位置。
经过一些调试后,我发现unsigned char*
中unsigned char*
向量中的unprocessedData
所指向的内存并不完整,指针地址是正确的,但某些内存块是不可读的。我通过打印processData
中的char
中的每个unsigned char*
找到了此信息。当生产者线程调用processData
时(这是cuda不会抛出异常的时候),所有processData
都可以很好地打印出来(我打印2048 * 2448 * 4 {{1 s,由上述图像分辨率和格式决定)。但是当消费者线程调用char
时,打印char
会抛出相同的异常,在第40 processData
周围抛出异常(大约40,不总是40)。
好的,现在我非常确定我的指针不只指向真实的内存位置,我也知道指针指向的第一个内存块保持预期值的次数与I&一样多已经对此进行了测试。为了测试这一点,我在char
中故意将char
42或producerTask
*)的测试值写入{{1指向的int
内存块}}。在char
函数中,我检查内存块是否仍然包含测试值,它确实存在。所以,现在我知道指针所指向的一些内存块由于某些未知原因而无法读取。此外,我的测试并没有证明第一个内存块不会变得无法访问,只是因为我做了少量的测试而无法访问它。 TLDR for Updates 1 to 3 :0
指针有效,它们指向实际内存地址,并且它们指向保持预期值的内存地址。
另一次调试尝试。现在我使用Visual Studio的内存窗口来直观地检查数据。调试器告诉我unsigned char*
指向processData
。这就是unprocessedImage
周围的内存:
记忆似乎合情合理,可以清楚地看到unProcessedData[0]
格式,图像全部为黑色,因此0x00000279d7c76070
通道接近0而0x00000279d7c76070
为RGBa
则有意义。我向下滚动了很长时间以查看内存的样子,一直到RGB
数据看起来很好(RGBa值如预期的那样)。 alpha
数字也有意义,因为ff
- 0x00000279D8F9606F
= 0x00000279D8F9606F
,这意味着有预期的20054016有效0x00000279D8F9606F
(2048高度* 2448宽度) * 4个频道= 20054016)。好的,到目前为止一切顺利。请注意,在运行cuda函数之前,这一切都是正确的。在单步执行cuda函数后,我得到了相同的异常:访问冲突读取位置0x00000279d7c76070
。请注意, 0d20054015
介于char
和0x00000279D80B8000
之间,我视觉检查的内存部分是正确的。现在,在运行cuda函数之后,0x00000279D80B8000
和0x00000279d7c76070
之间的内存如下所示:
0x00000279D8F9606F
0x00000279d7c76070
内的任何内容。指针指向的内存发生变化。所有0x00000279D8F9606F
都等同于cout
,如下图所示。 MSDN上的This页面说processData
但是当我从生产者线程中调用char
时,指向的内存在0xdd
之后就没有变化。现在对这个问题最热烈的评论告诉我要学习更多关于指针的内容。我目前正在这样做(希望我的更新可能会建议),但是我需要了解哪些主题?我知道指针是如何工作的。我知道我的指针指向有效的内存位置(参见更新2)。我知道指针指向的一些内存块变得无法读取(参见更新3)。但我不知道为什么内存块无法访问。特别是,我不知道为什么在从消费者线程调用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
时不会抛出异常)。还有什么我可以做的来帮助缩小这个问题吗?
答案 0 :(得分:0)
问题相当简单,当天的评论引导我走向正确的方向,我很感激。
在我的更新中,我提到使用cout
打印任何内容都会导致数据损坏。虽然看起来似乎发生了这种情况,但在fetch
和deposit
中加入一些断点之后,我对实际发生的情况有了全面的了解。
我制作图像数据的方式是使用随相机提供的另一个SDK,SDK为我提供了包装指针类型的图像数据。然后我转换了图像格式,然后展开转换后的图像以获得指向原始图像的指针。然后,指向原始图像的指针存储在producedDataVec
中,deposit
将其存入rawImageBuffer
。问题是,只要转换后的图像超出范围,我的数据就会被破坏。因此,cout
语句并不真正负责破坏我的数据。断点放在任何地方我都可以看到数据在转换后的图像超出范围后就会损坏。要解决这个问题,现在我的生产者直接deposit
指向缓冲区的包装指针。消费者fetch
是包装指针,通过转换消费者中的格式获得转换后的图像,然后获得原始图像指针。现在,转换后的图像仅在processData
返回后超出范围,因此永远不会抛出异常。