【发布时间】:2017-09-12 20:55:55
【问题描述】:
我有一些处理图像的代码。性能至关重要,所以我很想使用BoundedBuffer 实现多线程。图像数据存储为unsigned char*(由我用来处理图像数据的SDK决定)。
问题出现在消费者线程中调用的processData函数中。在processData 内部,还有另一个使用cudaMemcpy2D 函数的函数(来自图像处理SDK)。 cuda 函数总是抛出一个异常说访问冲突读取位置。
但是,如果我直接在生产者线程中调用processData 或deposit,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 图像的数据。
更新:
在cmets中有人指出
unsigned char*指针可能无效后,我发现指针指向的地址实际上是一个真实的内存位置。异常中访问冲突读取位置 X.X大于指针指向的位置。经过一番调试,我发现
unprocessedData中的unsigned char*指向的内存在processData中并没有保持原样,指针地址是正确的,但是有些块内存不可读。我通过在unsigned char*和processData中打印每个char发现了这一点。当生产者线程调用processData时(这是cuda 不抛出异常的时候),所有chars 都可以很好地打印出来(我打印的是2048*2448*4chars,由上述图像分辨率决定和格式)。但是当processData被消费者线程调用时,打印char会抛出相同的异常,在第40 个char左右(在第40 个左右,并不总是在第40 个左右)抛出异常。
1234563对此进行了测试。为了测试这一点,在又一次调试尝试。现在我正在使用 Visual Studio 的内存窗口来直观地检查数据。调试器告诉我
unProcessedData[0]指向0x00000279d7c76070。这是0x00000279d7c76070周围的内存的样子: 记忆似乎是明智的,RGBa格式可以清楚地看到,图像全黑,所以RGB通道接近 0 而alpha是ff是有道理的。我向下滚动了很长时间以查看内存的样子,一直到0x00000279D8F9606F数据看起来不错(RGBa 值符合预期)。0x00000279D8F9606F数字也很有意义,因为0x00000279D8F9606F-0x00000279d7c76070=0d20054015,这意味着有 20054016 个有效chars 是预期的(2048 高度*2448 宽度*4 通道 = 20054016)。好的,到目前为止一切顺利。请注意,所有这些都在运行 cuda 函数之前。单步执行 cuda 函数后,我得到了同样的异常:访问冲突读取位置0x00000279D80B8000。请注意,0x00000279D80B8000介于0x00000279d7c76070和0x00000279D8F9606F之间,这是我目测正确的内存部分。现在,运行 cuda 函数后,0x00000279d7c76070和0x00000279D8F9606F之间的内存如下所示:- 当我在调用 cuda 函数之前
coutprocessData中的任何内容时。指针指向的内存发生变化。所有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之后,指向的内存不会改变。
producerTask 中,我特意将测试值(例如int 42,或char *)写入unsigned char* 指向的0th 内存块。在processData 函数中,我检查内存块是否仍然包含测试值,并且确实如此。所以,现在我知道指针指向的一些内存块由于某种未知原因变得无法读取。此外,我的测试并不能证明第一个内存块不会变得不可访问,只是在我进行的少数测试中它并没有变得不可访问。 更新 1 到 3 的 TLDR:unprocessedImage 指针是有效的,它们指向一个真实的内存地址,它们也指向保存预期值的内存地址。
目前对这个问题最受好评的评论是告诉我更多地了解指针。我目前正在这样做(希望我的更新可能会暗示),但是我需要了解哪些主题?我确实知道指针是如何工作的。我知道我的指针指向有效的内存位置(请参阅更新 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