目前我正在开发一个包含数学分析的桌面应用程序。我使用 qt 进行 GUI 和用 C++ 编写的项目。
当用户开始分析时,我打开一个工作线程并启动一个进度条。到目前为止一切正常,当用户取消操作时问题开始。操作很复杂,我正在使用多个函数和对象,我在多个位置分配/释放内存有时。我想了解在取消操作中恢复应该做什么。因为可能存在内存泄漏。我应该使用哪种模式或方法来保证取消操作的鲁棒性和安全性?
我的想法是抛出异常,但操作确实很复杂,所以我应该将 try-catch 放入所有函数中,还是有更通用的方法,模式..
编辑:问题是我的对象在作用域之间传输,所以共享指针或自动指针没有解决我的问题,
标记想法可以,但我认为它需要这么多代码,应该有一个简单的方法。
Currently i am working on a desktop application which consists mathematical analysiss.I am using qt for GUI and project written in c++.
When user starts an analysis, i open a worker thread and start a progress bar.Everything is ok up to now, problem starts when user cancels operation.Operation is complex, i am using several functions and objects, i allocate/deallocate memory at several times.I want to learn what should i do for recovering in cancel operation.Because there can be memory leaks.Which pattern or method i should use to be robust and safe for cancelling operation?
My idea is throwing an exception, but operation is really complex so should i put try-catch to all of my functions or is there a more generic way, pattern..
Edit: Problem is my objects are transfered between scopes, so shared_ptr or auto_ptr doesnt solve my problem,
Flag idea can be, but i think it requires so much code and there should be an easy way.
发布评论
评论(8)
您应该尝试将动态分配的资源保存在自动(位于堆栈上的本地)哨兵对象中,当这些资源超出范围时,这些对象会在其析构函数中释放这些资源。 这样您就可以知道它们不会泄漏,即使函数由于异常而退出。 您可能还想研究用于在例程之间共享内存的boost 库shared_ptr。
You should try to hold dynamically allocated resources in automatic (locals that live on the stack) sentry objects which free those resources in their destructors when they go out of scope. This way you can know they aren't going to leak, even if a function exits because of an exception. You also might want to investigate the boost libraries shared_ptr for sharing memory between routines.
首先,在多线程应用程序中抛出异常是不确定的,因为没有标准的方法来处理它们(它们是否传播到其他线程?调度程序?main()?其他地方?)。 至少在您获得内置标准化线程的 C++0x 库之前是这样。
目前,使用 RAII 更有意义(这将保证在作用域退出时清除所有资源(包括内存),无论它是由于成功还是失败而存在),并将某种状态代码传递回最有意义的线程(例如调度程序)。
此外,十多年来一直不鼓励直接取消线程。 正如 Simon Jensen 所建议的那样,最好告诉线程自行停止并让线程处理清理工作。
First, throwing exceptions multi-threaded applications is iffy because there isn't a standard way to handle them (do they propagate to other threads? the scheduler? main()? somewhere else?). At least until you get a C++0x library which has standardized threading built in.
For the time being it makes more sense to use RAII (which will guarantee that all resources -- including memory -- is cleaned up when the scope exits, whether it exists due to success or failure) and have some sort of status code passed back to whichever thread makes the most sense (the scheduler for instance).
Also, directly canceling threads has been discouraged for more than a decade. It's much better to tell a thread to stop itself and have the thread handle the clean up, as Simon Jensen suggests.
这个问题没有通用的解决方案。
一些可能的策略:
但无论如何,要有策略,否则你会感到痛苦。
There is no general solution to this question.
Some possible strategies:
But in any case, have a strategy or you will feel the pain.
答案是,这取决于您操作的复杂程度。
这里有一些方法。
1)如前所述,在操作中放置一个“取消”标志,并让该操作定期(接近)间隔轮询取消标志,可能至少与更新进度栏一样频繁。 当用户点击取消时,然后点击取消例程。
现在,对于这种情况下的内存处理,我已经通过几种方式完成了。 我更喜欢使用智能指针或 STL 对象,它们会在超出范围时自行清理。 基本上,在具有析构函数的对象内部声明您的对象,该析构函数将为您处理内存清理; 当您创建这些对象时,会为您创建内存,并且当对象超出范围时,内存会自动删除。 您还可以添加类似“dispose”方法的内容来处理内存。 它可能看起来像这样:
如果你想变得非常聪明,你可以将该类模板化为任何对象的通用类,甚至可以拥有数组等。 当然,您可能必须在访问对象之前检查该对象是否已被释放,但是当您直接使用指针时,它们会中断。 您还可以内联 Access 方法,这样在执行期间就不会花费函数调用的费用。
2) goto 方法。 在前面声明你的内存,在最后删除,当你点击cancel方法时,调用goto转到方法的末尾。 我认为某些程序员可能会因此对你处以私刑,因为 goto 被认为是极其糟糕的风格。 由于我学习了 basic 和“goto 10”作为循环方式,所以它并没有让我那么害怕,但你可能必须在代码审查期间回答学究,所以你最好有一个非常好的解释为什么你选择这个而不是选项 1。3
)将其全部放入进程中,而不是线程中。 如果可以的话,将所有要操作的信息序列化到磁盘,然后在另一个程序中运行复杂的分析。 如果该程序死掉了,那就这样吧,它不会破坏您的主应用程序,并且如果您的分析如此复杂并且在 32 位计算机上,您可能需要所有内存空间才能运行。 您只需将进度读/写到磁盘,而不是使用共享内存来传递进度信息,并且可以立即取消。 实施起来有点棘手,但并非不可能,而且可能更稳定。
The answer is that it depends on the complexity of your operation.
There's a few approaches here.
1) as was mentioned, put a 'cancel' flag in the operation, and have that operation poll the cancel flag at regular (close) intervals, probably at least as often as you update your progress bar. When the user hits cancel, then hit the cancel routine.
Now, as for memory handling in this scenario, I've done it a couple of ways. My preference is to use smart pointers or STL objects that will clean themselves up when you go out of scope. Basically, declare your objects inside of an object that has a destructor that will handle memory cleanup for you; as you create these objects, memory is created for you, and as the object goes out of scope, the memory is removed automatically. You can also add something like a 'dispose' method to handle the memory. It could look like this:
If you want to get really clever, you can template that class to be generic for any of your objects, or even to have arrays and the suchlike. Of course, you might have to check to see if an object has been disposed prior to accessing it, but thems the breaks when you use pointers directly. You might also be able to inline the Access method, so that it doesn't cost you a function call during execution.
2) A goto method. Declare your memory at the front, delete at the end, and when you hit the cancel method, call goto to go to the end of the method. I think certain programmers might lynch you for this, as goto is considered extremely bad style. Since I learned on basic and 'goto 10' as a way for looping, it doesn't freak me out as much, but you might have to answer to a pedant during a code review, so you'd better have a really good explanation for why you went with this one and not option 1.
3) Put it all into a process, rather than a thread. If you can, serialize all of the information to manipulate to disk, and then run your complicated analysis in another program. If that program dies, so be it, it doesn't destroy your main application, and if your analysis is that complicated and on a 32 bit machine, you may need all of that memory space to run anyway. Rather than using shared memory for passing progress information, you just read/write progress to disk, and canceling is instant. It's a bit trickier to implement, but not impossible, and potentially far more stable.
由于您使用的是 Qt,因此您可以利用 QObject 的父内存系统。
您说您在工作线程运行期间分配和释放内存。 如果每个分配都是 QObject 的一个实例,为什么不将其作为当前 QThread 对象的父对象呢?
您可以随时删除它们,这很好,但如果您错过了一些,它们将在 QThread 释放时被清除。
请注意,当您告诉工作线程 QThread 取消时,您必须等待它完成才能删除 QThread 实例。
如果您使用 Simon Jensen 的答案来退出线程并使用 QObject 作为内存策略,我认为您的情况会很好。
Since you are using Qt, you can take advantage of QObject's parenting memory system.
You say that you allocate and deallocate memory during the run of your worker thread. If each allocation is an instance of QObject, why don't you just parent it to the current QThread Object?
You can delete them as you go and that's fine, but if you miss some, they will be cleaned up when the QThread is deallocated.
Note that when you tell your worker QThread to cancel, you must then wait for it to finish before you delete the QThread instance.
I you use Simon Jensen's answer to quit your thread and QObject as your memory strategy, I think you'll be in a good situation.
关闭工作线程的一种非常常见的方法是用一个标志对其进行标记,并让工作线程定期检查该标志。 如果被标记,它应该停止其工作流程,清理并退出。
在你的情况下有这种可能性吗?
A pretty common way to close down worker threads, is to mark it with a flag, and let the worker thread inspect this flag at regular intervals. If marked, it should discontinue its workflow, clean up and exit.
Is that a possibility in your situation?
工作线程应该检查是否有消息要停止。 该消息可以通过标志或事件来传递。 当收到停止消息时,线程应该退出。
对所有分配的内存使用 BOOST 安全指针。 退出时不会有内存泄漏。 曾经。
The worker thread should check for a message to stop. the message can be through a flag or an event. when stop message received the thread should exit.
USE BOOST safe pointers for all memory allocated. on exit you would have no memory leaks. ever.
确保
分配的内存由智能指针拥有,可以是 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 机制将确保正确释放所有分配的数据。
假设您有一些将在线程中调用的伪代码。 它可能类似于一个函数,它可能会分配内存,另一个函数会在循环内进行大量计算:
上面的代码不安全,如果不采取步骤来确保在任何情况下,无论中断模式如何,都会泄漏在所有时刻,内存都将由 C++ 对象(通常是智能指针)拥有。
可以通过这种方式进行修改,使代码既可中断又保证内存安全:
不使用 Boost?
如果你不使用Boost,那么你可以模拟这个。 如果线程应该被中断,则将一些线程存储类似布尔的变量设置为“true”。 添加检查此变量的函数,然后如果为 true,则抛出特定异常。 让线程的“根”捕获此异常以使其正确结束。
免责声明
我现在无法访问 Boost 1.37,因此无法测试以前的代码,但想法是存在的。 我将尽快对此进行测试,并最终发布更完整/正确/可编译的代码。
Be sure your allocated memory is owned
Be sure every allocated memory is owned by a smart pointer, either C++03's auto_ptr, C++11's unique_ptr or Boost's scoped_ptr, or even shared_ptr (which can be shared, copied and moved).
This way, RAII will protect you from any memory leak.
Use Boost.Thread 1.37
Read Interrupt Politely, an article from Herb Sutter explaining miscellaneous ways to interrupt a thread.
Today wth Boost.Thread 1.37, You can ask a thread to terminate by throwing an exception. In Boost, it's the boost::thread_interrupted exception, which will throw an exception from any interruption point.
Thus, you do not need to handle some kind of message loop, or verify some global/shared data. The main thread asks the worker thread to stop through an exception, and as soon as the worker thread reaches an interruption point, the exception is thrown. The RAII mecanism described earlier will make sure all your allocated data will be freed correctly.
Let's say you have some pseudo code that will be called in a thread. It could be something like a function that will perhaps allocated memory, and another that will do a lot of computation inside a loop:
The code above is not safe and will leak not matter the interruption mode if steps are not taken to be sure that at all moments the memory will be owned by a C++ object (usually, a smart pointer).
It could be modified this way to have the code be both interruptible, and memory safe:
Do not use Boost?
If you do not use Boost, then you can simulate this. Have some thread storage boolean-like variable set to "true" if the thread should be interrupted. Add functions checking this variable, and then throw a specific exception if true. Have the "root" of your thread catch this exception to have it end correctly.
Disclaimer
I don't have access to Boost 1.37 right now, so I'm unable to test the previous code, but the idea is there. I will test this as soon as possible, and eventually post a more complete/correct/compilable code.