C ++: Concurrency and destructors

Suppose you have an object that many threads can join. The critical section is used to protect sensitive areas. But what about the destructor? Even if I enter the critical section, as soon as I enter the destructor, as soon as the destructor has been called, is the object already invalid?

My train of thought: let's say I enter the destructor, and I have to wait in the critical section, because another thread is still using it. As soon as he finishes, I can finish the destruction of the object. It makes sense?

+4
source share
6 answers

In general, you should not destroy an object until you know that no other thread uses it. Period.

Consider this scenario based on your “thought process”:

  • Thread A: Get Link to Object X
  • Theme A: Lock Object X
  • Thread B: Get Link to Object X
  • Theme B: Lock Object X Lock
  • Theme A: Unlock Object X
  • Theme B: Lock Object X; unlock object X; destroy object X

Now consider what happens if the time is slightly different:

  • Thread A: Get Link to Object X
  • Thread B: Get Link to Object X
  • Theme B: Lock Object X; unlock object X; destroy object X
  • Subject: Lock Object X - Crash

In short, the destruction of an object must be synchronized somewhere other than the object itself. One common option is to use reference counting. In thread A, the link to the object itself will be blocked, which prevents the deletion of the link and the object being destroyed until it manages to increase the reference counter (keeping the object alive). Then, thread B simply clears the link and decreases the link count. You cannot predict which thread will actually call the destructor, but it will be safe anyway.

The reference counting model can be easily implemented using boost::shared_ptr or std::shared_ptr ; the destructor will not work until all shared_ptr in all threads are destroyed (or made elsewhere), so at the time of destruction, you know that the only pointer to the remaining object is the this pointer of the destructor itself.

Note that when using shared_ptr, it is important to prevent the original link to the object from being changed until you can grab a copy of it. For instance:

 std::shared_ptr<SomeObject> objref; Mutex objlock; void ok1() { objlock.lock(); objref->dosomething(); // ok; reference is locked objlock.unlock(); } void ok2() { std::shared_ptr<SomeObject> localref; objlock.lock(); localref = objref; objlock.unlock(); localref->dosomething(); // ok; local reference } void notok1() { objref->dosomething(); // not ok; reference may be modified } void notok2() { std::shared_ptr<SomeObject> localref = objref; // not ok; objref may be modified localref->dosomething(); } 

Please note that reading in shared_ptr at the same time is safe, so you can use read and write locks if that makes sense for your application.

+3
source

If an object is being used, you must ensure that the destructor of the object is not called before the end of the use of the object. If this is the behavior that you have, then this is a potential problem, and it really needs to be fixed.

You must make sure that if one thread destroys your objects, then the other thread must not call the functions on this object or the first thread must wait for the second thread to complete the function call.

Yes, even destructors may need critical sections to protect against updating some global data that is not related to the class itself.

+2
source

It is possible that while one thread is waiting for CS in the destructor, the other will destroy the object, and if the CS belongs to the object, it will also be destroyed. So a good design.

+1
source

Yes, when you are in the destructor, the object is no longer valid.

I used the Destroy () method, which enters the critical section, and then destroys it myself.

Is the object's lifetime expired before the destructor is called?

+1
source

Yes, it is normal. If the class supports this use, clients do not need to synchronize the destruction; that is, they don’t need to check that all other methods of the object are finished before calling the destructor.

I would recommend that clients do not assume that they can do this unless explicitly documented. Clients really carry this load by default with standard library objects (§17.6.4.10 / 2).

There are times when this is normal; std::condition_variable destructor, for example, specifically allows current calls to the condition_variable::wait() method when ~condition_variable() run. It only requires that clients do not start wait () calls after ~condition_variable() run.

Perhaps it would be easier to require the client to synchronize access to the destructor - and the constructor, if important - like most other standard libraries. I would recommend doing this if possible.

However, there are certain patterns where it makes sense to free clients from the burden of complete synchronization of destruction. condition_variable The general pattern looks like one: consider using an object that handles possibly long-running requests. The user performs the following actions:

  • Build Object
  • The reason the object receives requests from other threads.
  • The reason the object stops receiving requests: at the moment, some outstanding requests may be executed, but new ones cannot be called.
  • Destroy the object. The destructor is blocked until all requests are completed, otherwise current requests may have a bad time.

An alternative would be to require clients to synchronize access. You can imagine step 3.5 above when the client calls the shutdown() method on an object that blocks, after which the client can destroy the object. However, this project has some disadvantages; this complicates the API and introduces additional state for the shutdown-but-valid object.

Think instead, it is possible that step (3) is blocked until all requests are completed. There are compromises ...

+1
source

You absolutely need your facility lifetime to be shorter than consumer flows, or you have serious headaches. Or:

  • To make consumers of objects objects so that they do not exist outside your object, or
  • use message transfer / broker.

If you go the last route, I highly recommend 0mq http://www.zeromq.org/ .

0
source

All Articles