QFuture 内存泄漏

Posted

技术标签:

【中文标题】QFuture 内存泄漏【英文标题】:QFuture Memoryleak 【发布时间】:2018-03-05 09:26:39 【问题描述】:

我想并行化一个函数,但遇到几个小时后我的内存超载的问题。

测试程序计算了一些简单的东西,并且工作至今。只有内存使用量在不断增加。

QT 项目文件:

QT -= gui
QT += concurrent widgets
CONFIG += c++11 console
CONFIG -= app_bundle
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp

QT程序文件:

#include <QCoreApplication>
#include <qdebug.h>
#include <qtconcurrentrun.h>

double parallel_function(int instance)
    return (double)(instance)*10.0;


int main(int argc, char *argv[])

    QCoreApplication a(argc, argv);
    int nr_of_threads = 8;
    double result_sum,temp_var;
    for(qint32 i = 0; i<100000000; i++)
      QFuture<double> * future = new QFuture<double>[nr_of_threads];

      for(int thread = 0; thread < nr_of_threads; thread++)
        future[thread] = QtConcurrent::run(parallel_function,thread);
      
      for(int thread = 0; thread < nr_of_threads; thread++)
        future[thread].waitForFinished();
        temp_var = future[thread].result();
        qDebug()<<"result: " << temp_var;
        result_sum += temp_var;
      
  

  qDebug()<<"total: "<<result_sum;
  return a.exec();

正如我所观察到的,QtConcurrent::run(parallel_function,thread) 分配内存,但在future[thread].waitForFinished() 之后不释放内存。

这里有什么问题?

【问题讨论】:

【参考方案1】:

你有内存泄漏,因为future 数组没有被删除。在外部for循环的末尾添加delete[] future

for(qint32 i = 0; i<100000000; i++)

   QFuture<double> * future = new QFuture<double>[nr_of_threads];

   for(int thread = 0; thread < nr_of_threads; thread++)
     future[thread] = QtConcurrent::run(parallel_function,thread);
   
   for(int thread = 0; thread < nr_of_threads; thread++)
      future[thread].waitForFinished();
      temp_var = future[thread].result();
      qDebug()<<"result: " << temp_var;
      result_sum += temp_var;
   

   delete[] future; // <--

【讨论】:

没有人应该编写使用原始 new/delete 的 C++ 代码......这是非常糟糕的建议。避免内存泄漏的方法不是更努力地进行手动内存管理。避免内存泄漏的方法是在设计上不可能发生内存泄漏的地方编写代码。这意味着编写 C++,而不是 C-using-C++-syntax。【参考方案2】:

这就是它的外观 - 请注意一切都可以变得多么简单!你已经死心塌地做手动内存管理:为什么?首先,QFuture 是一个值。您可以将其非常有效地存储在将为您管理内存的任何矢量容器中。您可以使用 range-for 迭代这样的容器。等等。

QT = concurrent   # dependencies are automatic, you don't use widgets
CONFIG += c++14 console
CONFIG -= app_bundle
SOURCES = main.cpp

尽管这个例子是综合的并且map_function 非常简单,但值得考虑的是如何最有效和最有表现力地做事。您的算法是典型的 map-reduce 操作,blockingMappedReduce 的开销是手动完成所有工作的一半。

首先,让我们用 C++ 重铸原来的问题,而不是一些 C 加弗兰肯斯坦。

// https://github.com/KubaO/***n/tree/master/questions/future-ranges-49107082
/* QtConcurrent will include QtCore as well */
#include <QtConcurrent>
#include <algorithm>
#include <iterator>

using result_type = double;

static result_type map_function(int instance)
   return instance * result_type(10);


static void sum_modifier(result_type &result, result_type value) 
   result += value;


static result_type sum_function(result_type result, result_type value) 
   return result + value;


result_type sum_approach1(int const N) 
   QVector<QFuture<result_type>> futures(N);
   int id = 0;
   for (auto &future : futures)
      future = QtConcurrent::run(map_function, id++);
   return std::accumulate(futures.cbegin(), futures.cend(), result_type, sum_function);

没有手动内存管理,也没有显式拆分为“线程”——这是没有意义的,因为并发执行平台知道有多少线程。所以这已经更好了!

但这似乎很浪费:每个未来在内部至少分配一次(!)。

我们可以使用 map-reduce 框架,而不是为每个结果显式使用期货。为了生成序列,我们可以定义一个迭代器来提供我们希望处理的整数。迭代器可以是正向或双向的,它的实现是 QtConcurrent 框架所需的最低限度。

#include <iterator>

template <typename tag> class num_iterator : public std::iterator<tag, int, int, const int*, int> 
   int num = 0;
   using self = num_iterator;
   using base = std::iterator<tag, int, int, const int*, int>;
public:
   explicit num_iterator(int num = 0) : num(num) 
   self &operator++()  num ++; return *this; 
   self &operator--()  num --; return *this; 
   self &operator+=(typename base::difference_type d)  num += d; return *this; 
   friend typename base::difference_type operator-(self lhs, self rhs)  return lhs.num - rhs.num; 
   bool operator==(self o) const  return num == o.num; 
   bool operator!=(self o) const  return !(*this == o); 
   typename base::reference operator*() const  return num; 
;

using num_f_iterator = num_iterator<std::forward_iterator_tag>;

result_type sum_approach2(int const N) 
   auto results = QtConcurrent::blockingMapped<QVector<result_type>>(num_f_iterator0, num_f_iteratorN, map_function);
   return std::accumulate(results.cbegin(), results.cend(), result_type, sum_function);


using num_b_iterator = num_iterator<std::bidirectional_iterator_tag>;

result_type sum_approach3(int const N) 
   auto results = QtConcurrent::blockingMapped<QVector<result_type>>(num_b_iterator0, num_b_iteratorN, map_function);
   return std::accumulate(results.cbegin(), results.cend(), result_type, sum_function);

我们可以放弃std::accumulate 并改用blockingMappedReduced 吗?当然:

result_type sum_approach4(int const N) 
   return QtConcurrent::blockingMappedReduced(num_b_iterator0, num_b_iteratorN,
                                              map_function, sum_modifier);

我们也可以试试随机访问迭代器:

using num_r_iterator = num_iterator<std::random_access_iterator_tag>;

result_type sum_approach5(int const N) 
   return QtConcurrent::blockingMappedReduced(num_r_iterator0, num_r_iteratorN,
                                              map_function, sum_modifier);

最后,我们可以从使用范围生成迭代器切换到预先计算的范围:

#include <numeric>

result_type sum_approach6(int const N) 
   QVector<int> sequence(N);
   std::iota(sequence.begin(), sequence.end(), 0);
   return QtConcurrent::blockingMappedReduced(sequence, map_function, sum_modifier);

当然,我们的目的是对所有内容进行基准测试:

template <typename F> void benchmark(F fun, double const N) 
   QElapsedTimer timer;
   timer.start();
   auto result = fun(N);
   qDebug() << "sum:" << fixed << result << "took" << timer.elapsed()/N << "ms/item";


int main() 
   const int N = 1000000;
   benchmark(sum_approach1, N);
   benchmark(sum_approach2, N);
   benchmark(sum_approach3, N);
   benchmark(sum_approach4, N);
   benchmark(sum_approach5, N);
   benchmark(sum_approach6, N);

在我的系统上,在发布版本中,输出是:

sum: 4999995000000.000000 took 0.015778 ms/item
sum: 4999995000000.000000 took 0.003631 ms/item
sum: 4999995000000.000000 took 0.003610 ms/item
sum: 4999995000000.000000 took 0.005414 ms/item
sum: 4999995000000.000000 took 0.000011 ms/item
sum: 4999995000000.000000 took 0.000008 ms/item

请注意,在随机可迭代序列上使用 map-reduce 的开销比使用 QtConcurrent::run 低 3 个数量级以上,并且比非随机可迭代解决方案快 2 个数量级。

【讨论】:

写得好漂亮的答案。

以上是关于QFuture 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

MFC内存泄漏调试

如何防止java中的内存泄漏

记录一次DialogFragment 内存泄漏

常见的内存泄漏原因及解决方法

Android ValueAnimator --内存泄漏

Android内存泄漏查找和解决