为什么这个 std::queue/指向结构的指针列表直到 List.Size() == 0 才释放内存?

Why does this std::queue/list of pointers to structs not free memory until List.Size() == 0?

本文关键字:Size List 内存 释放 列表 queue std 指针 结构 为什么      更新时间:2023-10-16

我有一个程序,它将指向包含 cv::mat 克隆的结构的指针馈送到其他线程使用的 std::queue/std::list(尝试了两者(。

读取/创建速度很快,但消耗速度很慢,因此队列的大小会随着时间的推移而增长。
此队列变得巨大,很容易占用>50% 的系统内存。

当读取/创建停止时,队列会缩小,但内存不会!
当队列大小最终达到 0 时,内存几乎立即消失。queue.size(( == 0 触发器可以通过确保队列永远不会弹出最后一个指针来确认 - 内存不会消失。 *注意:队列仍然存在,它没有超出范围,这是静态的。

所以我有两个问题:

  1. 为什么队列达到零大小时内存消失?或者换句话说,为什么内存不会随着指针被消耗/删除而消失?

  2. 如何显式释放内存?


代码是这样的:

struct MyStruct {
cv::mat matA;
~MyStruct(){
cout << "Yeah, destructor is called!" << endl;
//matA.release(); //Not needed, right? Adding it does not change anything.
}
};
static queue<shared_ptr<MyStruct>> awaitingProcessing;
static mutex queueGuard;

线程 1(队列填充器(:

BYTE* buffer = (BYTE*)malloc(dataWidth*dataHeight);
while(signal){
LoadData(buffer);
cv::Mat data = cv::Mat(dataHeight, dataWidth, CV_8U, buffer);

auto addable = shared_ptr<MyStruct>(new MyStruct())>;
addable->matA = data.clone();
lock_guard<mutex> guard(queueGuard);
awaitingProcessing.push(addable);
}

线程 2(使用者(:

shared_ptr<MyStruct> pullFromQueue(){
lock_guard<mutex> guard(queueGuard);
if (awaitingProcessing.size() > 0){
auto returnable = awaitingProcessing.front();
awaitingProcessing.pop();
return returnable;
}
return nullptr;
}
void threadLogic(){
while (!interruptFlag){
auto ptr = pullFromQueue();
if (ptr == nullptr){
usleep(5);
}
else{
doSomething(ptr);
}
// ptr destructor called here, as refcount properly hits zero
} 
}

如果我没记错的话,std 数据收集通常不会释放它们的内存并将其保留为保留,以防大小再次增长。但是,此集合(队列和/或列表(由指针组成,因此即使队列变大,内存占用也应该很小。

我不熟悉OpenCV的内存管理,但它似乎正在做类似的事情。暂停队列填充允许队列大小缩小,但内存不会缩小。然后,恢复填充会增加队列大小,而不会增加内存大小。


总结几个关键点:

内存
  • 确实会在不更改范围的情况下释放(即不是内存泄漏(
  • 仅当队列大小达到零时,内存才会释放。如果队列大小永远保持在 1,则不会释放
  • 结构被破坏
  • 结构包含克隆的 cv::mats(我认为这是关键点(
  • 列表/队列仅包含指针,因此应该很小

std::queue默认使用std::deque作为内部容器。当内存被释放时,实现在很大程度上被定义(当大小达到零时可能是这种情况(,但std::dequestd::vector确实具有释放多余内存的功能,即shrink_to_fit(c ++ 11功能(。这在std::queue接口中不可用,但可以通过继承来完成(队列中的容器是protected(。

伪代码

template<class T>
struct shrinkable_queue : public std::queue<T> {
void shrink_to_fit() {c.shrink_to_fit();}
};

您也可以使用std::queue<T, std::list<T>>。我确实检查了 MSVC 实现,因为您说您也尝试了列表,至少在我的版本中,即使在删除单个节点(如预期的那样(时,它似乎也会解除分配内存。