目前我正在开发一个包含数学分析的桌面应用程序。我使用qt进行GUI和用c ++编写的项目。 当用户开始分析时,我打开一个工作线程并启动一个进度条。到目前为止一切正常,当用户取消操作时问题开始。操作很复杂,我使用了几个函数和对象,我在几个分配/解除分配内存我希望了解如何在取消操作中恢复。因为可能存在内存泄漏。我应该使用哪种模式或方法来强制安全地取消操作?
我的想法是抛出异常,但是操作真的很复杂,所以我应该把try-catch放到我的所有函数中,还是有更通用的方式,模式..
编辑:问题是我的对象是在作用域之间转移的,所以shared_ptr或auto_ptr不能解决我的问题, 国旗的想法可以,但我认为它需要这么多的代码,应该有一个简单的方法。
答案 0 :(得分:9)
关闭工作线程的一种非常常见的方法是使用标志标记它,并让工作线程定期检查此标志。如果已标记,则应停止其工作流程,清理并退出。
这种可能性适用于您的情况吗?
答案 1 :(得分:4)
工作线程应检查要停止的消息。消息可以通过标志或事件。当收到停止消息时,线程应退出。
USE BOOST分配所有内存的安全指针。在退出时你将没有内存泄漏。如初。
答案 2 :(得分:2)
确保每个分配的内存都由智能指针拥有,可以是C ++ 03的auto_ptr,C ++ 11的unique_ptr或Boost的scoped_ptr,甚至是shared_ptr(可以共享,复制和移动)。< / p>
这样,RAII可以保护您免受任何内存泄漏。
阅读Interrupt Politely,一篇来自Herb Sutter的文章,解释了打断一个帖子的各种方法。
今天和Boost.Thread 1.37一样,您可以通过抛出异常来请求线程终止。在Boost中,它是boost :: thread_interrupted异常,它将从任何interruption point引发异常。
因此,您不需要处理某种消息循环,也不需要验证某些全局/共享数据。主线程要求工作线程停止异常,并且只要工作线程到达中断点,就会抛出异常。前面描述的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,那么您可以模拟它。如果线程应该被中断,请将一些线程存储类似于boolean的变量设置为“true”。添加检查此变量的函数,如果为true则抛出特定异常。让线程的“根”捕获此异常以使其正确结束。
我现在无权访问Boost 1.37,所以我无法测试之前的代码,但是这个想法就在那里。我将尽快测试,并最终发布更完整/正确/可编译的代码。
答案 3 :(得分:0)
您应该尝试在自动(堆栈中存在的本地)中保留动态分配的资源,这些资源在超出范围时会在其析构函数中释放这些资源。通过这种方式,您可以知道它们不会泄漏,即使函数因异常而退出也是如此。您还可能需要调查boost库shared_ptr以在例程之间共享内存。
答案 4 :(得分:0)
首先,抛出异常多线程应用程序是不确定的,因为没有一种标准的方法来处理它们(它们是否传播到其他线程?调度程序?main()?在其他地方?)。至少在你得到一个内置标准化线程的C ++ 0x库之前。
目前使用RAII更有意义(它可以保证所有资源 - 包括内存 - 在范围退出时被清除,是否由于成功或失败而存在)并具有某种状态代码传递回最合适的线程(例如调度程序)。
此外,十多年来一直不鼓励直接取消线程。正如Simon Jensen建议的那样,告诉线程停止自己并让线程处理清理会好得多。
答案 5 :(得分:0)
这个问题没有一般解决方案。
一些可能的策略:
但无论如何,有策略或者你会感到痛苦。
答案 6 :(得分:0)
答案是,这取决于您的操作的复杂程度。
这里有几种方法。 1)如上所述,在操作中放置一个“取消”标志,并让该操作以常规(关闭)间隔轮询取消标记,可能至少与更新进度条一样频繁。当用户点击取消时,然后点击取消例程。
现在,关于这种情况下的内存处理,我已经完成了几个方面。我的偏好是使用智能指针或STL对象,当你超出范围时它们会自我清理。基本上,在具有析构函数的对象中声明对象,该析构函数将为您处理内存清理;在创建这些对象时,会为您创建内存,并且当对象超出范围时,将自动删除内存。您还可以添加类似'dispose'方法来处理内存。它看起来像这样:
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方法。在前面声明你的内存,在最后删除,当你点击cancel方法时,调用goto去到方法的结尾。我认为某些程序员可能会因此而嘲笑你,因为goto被认为是非常糟糕的风格。由于我在基础和'goto 10'上学习循环,它并没有让我感到惊讶,但你可能不得不在代码审查期间回答一个学究,所以你最好有一个很好的解释为什么你选择这个而不是选项1.
3)把它全部放入一个进程,而不是一个线程。如果可以,序列化所有信息以操作磁盘,然后在另一个程序中运行复杂的分析。如果该程序死了,那么它就不会破坏你的主应用程序,如果你的分析很复杂并且在32位机器上,你可能还需要所有的内存空间来运行。您只需将进度读/写到磁盘,即可立即取消,而不是使用共享内存来传递进度信息。实施起来有点棘手,但并非不可能,而且可能更加稳定。
答案 7 :(得分: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作为你的记忆策略,我认为你会处于良好状态。