A virtual call inside a thread ignores a derived class

In the following program, I have a virtual call from a thread:

#include <iostream> #include <string> #include <thread> #include <mutex> #include <condition_variable> class A { public: virtual ~A() { t.join(); } virtual void getname() { std::cout << "I am A.\n"; } void printname() { std::unique_lock<std::mutex> lock{mtx}; cv.wait(lock, [this]() {return ready_to_print; }); getname(); }; void set_ready() { std::lock_guard<std::mutex> lock{mtx}; ready_to_print = true; cv.notify_one(); } void go() { t = std::thread{&A::printname,this}; }; bool ready_to_print{false}; std::condition_variable cv; std::mutex mtx; std::thread t{&A::printname,this}; }; class B : public A { public: int x{4}; }; class C : public B { void getname() override { std::cout << "I am C.\n"; } }; int main() { C c; A* a{&c}; a->getname(); a->set_ready(); } 

I was hoping the program would print:

 I am C. I am C. 

But instead, it prints :

 I am C. I am A. 

In the program, I wait until the derived object is completely constructed before I call the virtual member function. However, the thread starts before the object is fully constructed.

How can a virtual call be guaranteed?

+7
c ++ multithreading thread-safety
source share
3 answers

The code shown shows race status and undefined behavior.

In your main ():

 C c; // ... a->set_ready(); 

Immediately after returning set_ready() , the execution thread leaves main() . This leads to the immediate destruction of c , starting with the superclass c and continuing to destroy B , then A

c declared automatically. This means that as soon as main() returns, it has disappeared. Joined the invisible choir. This is no longer the case. He ceased to exist. This is an ex object.

Your join() is in the superclass destructor . Nothing stops c from destruction. The destructor will only pause and wait to join the thread when the superclass is destroyed, but c starts to be destroyed immediately!

Once the superclass of class c is destroyed, its virtual method will no longer exist, and the call to the virtual function will complete the virtual function in the base class.

Meanwhile, another executable thread is expecting a mutex and a condition variable. The condition of the race is that you have no guarantee that another execution thread will wake up and begin execution before the parent thread destroys c , which it does immediately after signaling the condition variable.

Everything that signals a condition variable gives you the fact that any thread of execution rotates on a condition variable, this thread of execution will start execution. Eventually. This thread could, on a very busy server, start executing seconds later, after it has been signaled through a condition variable. His object is long gone. It was in automatic mode, and main() destroyed it (or rather, the subclass c has already been destroyed, and A destructor is waiting to join the stream).

The behavior you observe is the parent thread controlling the destruction of the superclass c before std::thread around the invocation of the virtual method after receiving the signal from the condition variable and unlocking its mutex.

This is a race condition.

In addition, making a virtual method call while destroying the virtual object is no longer a starter. This behavior is undefined. Even if a thread of execution ends in an overridden method, its object is simultaneously destroyed by another thread. At this moment you are very screwed up, no matter how you turn.

Lesson Learn: Setting> to do something in the this object is a minefield of undefined behavior. There are ways to do it right, but it's hard.

+9
source share

This is the most likely sequence of events:

  • Built part of the object that starts the stream
  • Line B of the object is constructed.
  • Line C of the object is constructed.
  • getname is called in the main thread, which prints "I'm C!". because it is C.
  • The main thread notifies another thread (I will call it print thread)
  • main starts to return.
  • Part C of the object is destroyed.
  • Part B of the facility is destroyed.
  • Part of the object is destroyed ... but this is blocked until the print stream exits.
  • Now that the main thread is blocked, the OS switches to the print stream. A.
  • The print thread calls getname , which prints "I am!". because it is A (with particles C and B of the object that have now been destroyed).
  • Print output
  • The main thread wakes up, finishes the destruction of part A and exits the program.

To ensure the expected behavior is reliable, you need to wait for the print stream to finish before closing } main .

+2
source share

Other answers are final but do not show a possible correction. Here is the same program with additional variables and wait:

 #include <iostream> #include <string> #include <thread> #include <mutex> #include <condition_variable> class A { public: virtual ~A() { t.join(); } virtual void getname() { std::cout << "I am A.\n"; } void printname() { std::unique_lock<std::mutex> lock{mtx}; cv.wait(lock, [this]() {return ready_to_print; }); getname(); printing_done = true; cv.notify_one(); }; void set_ready() { std::lock_guard<std::mutex> lock{mtx}; ready_to_print = true; cv.notify_one(); } void go() { t = std::thread{&A::printname,this}; }; bool ready_to_print{false}; bool printing_done{false}; std::condition_variable cv; std::mutex mtx; std::thread t{&A::printname,this}; }; class B : public A { public: int x{4}; }; class C : public B { public: ~C() { std::unique_lock<std::mutex> lock{mtx}; cv.wait(lock, [this]() {return printing_done; }); } void getname() override { std::cout << "I am C.\n"; } }; int main() { C c; A* a{&c}; a->getname(); a->set_ready(); } 

Fingerprints :

 I am C. I am C. 
0
source share

All Articles