I was going through a legacy code and found the following snippet:
MyClass::~MyClass()
{
EnterCriticalSection(&cs);
//Access Data Members, **NO Global**
If you're accessing global variables you might need thread safety, yes
eg. My "Window" class adds itself to the list "knownWindows" in the constructor and removes itself in the destructor. "knownWindows" needs to be threadsafe so they both lock a mutex while they do it.
On the other hand, if your destructor only accesses members of the object being destroyed, you have a design issue.
Not going to make a difference. If, as you say, the order of the calls is reversed then you're calling a member function on a destructed object and that's going to fail. Synchronization can't fix that logical error (for starters, the the member function call would be trying to acquire a lock object that's been destructed).
I think you have a more fundamental problem. It shouldn't be legal to destroy your object on one thread while another thread is still calling member functions. This in itself is wrong.
Even if you successfully guard your destructor with critical sections, what happens when the other thread starts executing the remainder of the function? It will be doing so on a deleted object which (depending on it's allocation location) will be garbage memory or simple an invalid object.
You need to alter your code to ensure the object is not destructed while still in use.
I have seen a case with ACE threads, where the thread is running on an ACE_Task_Base object and the object is destroyed from another thread. The destructor acquires a lock and notifies the contained thread, just before waiting on a condition. The thread that is running on the ACE_Task_Base signal signals the condition at exit and lets the destructor complete and the first thread exit:
class PeriodicThread : public ACE_Task_Base
{
public:
PeriodicThread() : exit_( false ), mutex_()
{
}
~PeriodicThread()
{
mutex_.acquire();
exit_ = true;
mutex_.release();
wait(); // wait for running thread to exit
}
int svc()
{
mutex_.acquire();
while ( !exit_ ) {
mutex_.release();
// perform periodic operation
mutex_.acquire();
}
mutex_.release();
}
private:
bool exit_;
ACE_Thread_Mutex mutex_;
};
In this code, the destructor must use thread safety techniques to guarantee that the object is not destroyed before the thread that is running svc() exits.
Your comments says "NO Global members are being accessed here" so I'd guess not. Only the thread that created an object should destroy it, so from what other thread would you be protecting it?
I like orderly creation and destruction myself, where only a single object ever owns another sub-object, and any other object with a reference to that sub-object is a descendant further down in the tree. If any of those sub-objects represent different threads, then they will have made sure to complete before destruction proceeds up the tree.
Example:
The destructors for object A and B don't need to think about threads at all, and object C's destructor only needs to implement some communication mechanism (waiting on an event, for instance) with the thread it chose to create itself.
You can only get into trouble if you start handing out references (pointers) to your objects to arbitrary threads without keeping track of when those threads are created and destroyed, but if you're doing that then you should be using reference counting, and if you are then it's too late by the time the destructor is called. If there's still a reference to an object, then no one should have even tried to invoke its destructor.
Old question, but still valid IMHO.
In general, public members of a class altering a critical section, accessible from different threads should lock this critical section. But destruction of an object is the ultimate alteration of the state of an object including the critical section.
So if there are async operations going on, where this critical state of the object is entered, destruction should definitely wait until that critical section is left again. One way to do it, is to use locking in the destructor. Surely that doesn't help to guarantee the object itself isn't accessed erroneously anymore afterwards.
But that technique can be used to sync. destruction of the object with ending of the async. operation on the critical section.