2

目前我正在开发一个包含数学分析的桌面应用程序。我正在使用 qt 作为 GUI 和用 C++ 编写的项目。当用户开始分析时,我打开一个工作线程并启动一个进度条。到目前为止一切正常,当用户取消操作时问题开始。操作很复杂,我使用了几个函数和对象,我在几个分配/释放内存次。我想了解在取消操作中恢复应该怎么做。因为可能存在内存泄漏。我应该使用哪种模式或方法来保证取消操作的稳健性和安全性?

我的想法是抛出异常,但是操作真的很复杂,所以我应该将 try-catch 放到我的所有函数中,还是有更通用的方式,模式..

编辑:问题是我的对象在作用域之间传输,所以 shared_ptr 或 auto_ptr 不能解决我的问题,Flag 想法可以,但我认为它需要这么多代码,应该有一个简单的方法。

4

8 回答 8

9

关闭工作线程的一种非常常见的方法是用一个标志标记它,并让工作线程定期检查这个标志。如果被标记,它应该停止其工作流程,清理并退出。

你的情况有这种可能吗?

于 2009-01-08T21:18:41.543 回答
4

工作线程应检查是否有消息停止。消息可以通过标志或事件。当收到停止消息时,线程应该退出。

为所有分配的内存使用 BOOST 安全指针。退出时你不会有内存泄漏。曾经。

于 2009-01-08T21:31:56.103 回答
2

确保您分配的内存是拥有的

确保每个分配的内存都由一个智能指针拥有,无论是 C++03 的 auto_ptr、C++11 的 unique_ptr 还是 Boost 的 scoped_ptr,甚至是shared_ptr(可以共享、复制和移动)。

这样,RAII 将保护您免受任何内存泄漏。

使用Boost.Thread 1.37

礼貌地阅读中断,Herb Sutter 的一篇文章解释了中断线程的各种方法。

今天使用Boost.Thread 1.37,您可以通过抛出异常来要求线程终止。在 Boost 中,是 boost::thread_interrupted 异常,它会从任何中断点抛出异常。

因此,您不需要处理某种消息循环,或验证某些全局/共享数据。主线程通过异常请求工作线程停止,一旦工作线程到达中断点,就会抛出异常。前面描述的 RAII 机制将确保您分配的所有数据都将被正确释放。

假设您有一些将在线程中调用的伪代码。它可能是一个可能会分配内存的函数,另一个会在循环内进行大量计算:

Object * allocateSomeObject()
{
   Object * o = NULL ;

   if(/*something*/)
   {
      // Etc.
      o = new Object() ;
      // Etc.
   }

   return o ; // o can be NULL
}

void doSomethingLengthy()
{
   for(int i = 0; i < 1000; ++i)
   {
      // Etc.
      for(int j = 0; j < 1000; ++j)
      {
         // Etc.
         // transfert of ownership
         Object * o = allocateSomeObject() ;
         // Etc.
         delete o ;
      }
      // Etc.
   }
}

上面的代码不安全,如果不采取措施确保内存始终由 C++ 对象(通常是智能指针)拥有,则无论中断模式如何都会泄漏。

可以通过这种方式对其进行修改,以使代码既可中断又内存安全:

boost::shared_ptr<Object> allocateSomeObject()
{
   boost::shared_ptr<Object> o ;

   if(/*something*/)
   {
      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
      o = new Object() ;
      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
   }

   return o ; // o can be "NULL"
}

void doSomethingLengthy()
{
   for(int i = 0; i < 1000; ++i)
   {
      // Etc.
      for(int j = 0; j < 1000; ++j)
      {
         // Etc.
         // transfert of ownership
         boost::shared_ptr<Object> o = allocateSomeObject() ;
         // Etc.
         boost::this_thread::interruption_point() ;
         // Etc.
      }

      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
   }
}

void mainThread(boost::thread & worker_thread)
{
   // etc.
   if(/* some condition */)
   {
      worker_thread.interrupt() ;
   }
}

不使用升压?

如果你不使用 Boost,那么你可以模拟这个。如果线程应该被中断,则将一些线程存储布尔变量设置为“true”。添加检查此变量的函数,如果为真,则抛出特定异常。让线程的“根”捕获此异常以使其正确结束。

免责声明

我现在无法访问 Boost 1.37,所以我无法测试之前的代码,但想法就在那里。我会尽快对此进行测试,并最终发布更完整/正确/可编译的代码。

于 2009-01-10T02:36:14.030 回答
0

您应该尝试将动态分配的资源保存在自动(位于堆栈上的本地)哨兵对象中,当这些资源超出范围时,它们会在其析构函数中释放这些资源。这样您就可以知道它们不会泄漏,即使函数由于异常而退出。您可能还想研究用于在例程之间共享内存的 boost 库 shared_ptr。

于 2009-01-08T21:27:15.957 回答
0

首先,抛出异常多线程应用程序是不确定的,因为没有标准的方法来处理它们(它们是否传播到其他线程?调度程序?main()?其他地方?)。至少在你得到一个内置标准化线程的 C++0x 库之前。

目前使用 RAII 更有意义(这将保证在范围退出时清理所有资源(包括内存),无论它是由于成功还是失败而存在)并返回某种状态代码对于最有意义的线程(例如调度程序)。

此外,十多年来一直不鼓励直接取消线程。正如 Simon Jensen 建议的那样,告诉线程停止自己并让线程处理清理工作要好得多。

于 2009-01-08T21:32:03.123 回答
0

这个问题没有通用的解决方案。

一些可能的策略:

  • 有时使用 shared_ptrs 和朋友会有所帮助
  • 如果您不希望取消功能使您的算法混乱,请考虑抛出。抓住顶层函数并从那里清理。
  • 无论您放在堆栈上而不是堆上,都不会导致泄漏
  • 对于堆上在类之间有很多指针的大型结构,通常需要严格提供一种方法来释放整个内存结构。
  • 您是否考虑在取消时丢弃的内存池中放置新的?

但无论如何,要有策略,否则你会感到痛苦。

于 2009-01-08T21:32:40.737 回答
0

答案是这取决于您的操作的复杂性。

这里有几种方法。1)如前所述,在操作中放置一个“取消”标志,并让该操作定期(关闭)间隔轮询取消标志,可能至少与您更新进度条的频率一样。当用户点击取消时,然后点击取消例程。

现在,对于这种情况下的内存处理,我已经做了几种方法。我的偏好是使用智能指针或 STL 对象,它们会在超出范围时自行清理。基本上,在一个对象中声明你的对象,该对象有一个可以为你处理内存清理的析构函数;当您创建这些对象时,会为您创建内存,并且当对象超出范围时,内存会自动删除。您还可以添加诸如“处置”方法之类的东西来处理内存。它可能看起来像这样:

class MySmartPointer { 
     Object* MyObject;
     MySmartPointer() { MyObject = new Object(); }
     ~MySmartPointer() { if (MyObject != null) { delete MyObject; MyObject = null; }}
     void Dispose() { if (MyObject != null) { delete MyObject; MyObject = null; } }
     Object* Access() { return MyObject; }
 }

如果您想变得非常聪明,可以将该类模板化为任何对象的通用模板,甚至可以具有数组等。当然,您可能必须在访问对象之前检查它是否已被释放,但是当您直接使用指针时它们会中断。您可能还可以内联 Access 方法,这样在执行期间就不会花费您的函数调用。

2) goto 方法。前面声明你的内存,最后delete,当你打cancel方法的时候,调用goto去到方法的最后。我认为某些程序员可能会为此对你处以私刑,因为 goto 被认为是非常糟糕的风格。因为我学习了基本和“goto 10”作为循环的一种方式,所以它并没有让我害怕,但你可能需要在代码审查期间回答一个学究,所以你最好有一个很好的解释为什么你选择这个而不是选项 1。

3)把它全部放入一个进程,而不是一个线程。如果可以的话,将所有信息序列化到磁盘上,然后在另一个程序中运行复杂的分析。如果那个程序死了,就这样吧,它不会破坏你的主应用程序,如果你的分析很复杂并且在 32 位机器上,你可能需要所有的内存空间来运行。而不是使用共享内存来传递进度信息,您只需将进度读/写到磁盘,并且取消是即时的。实现起来有点棘手,但并非不可能,而且可能更稳定。

于 2009-01-08T21:33:14.963 回答
0

由于您使用的是 Qt,因此您可以利用 QObject 的父内存系统。

您说您在工作线程运行期间分配和释放内存。如果每个分配都是 QObject 的一个实例,为什么不把它作为当前 QThread 对象的父对象呢?

MyObject * obj = new MyObject( QThread::currentThread() );

您可以随时删除它们,这很好,但如果您遗漏了一些,它们将在 QThread 被释放时被清除。

请注意,当您告诉您的工作人员 QThread 取消时,您必须等待它完成,然后才能删除 QThread 实例。

workerThread->cancel();  // reqfuest that it stop
workerThread->wait();    // wait for it to complete
delete workerThread;     // deletes all child objects as well

如果您使用 Simon Jensen 的回答来退出您的线程并使用 QObject 作为您的内存策略,我认为您的情况会很好。

于 2009-01-11T07:52:32.457 回答