在以下程序中,我从一个线程中进行虚拟调用:
#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 am C.
I am C.
I am C.
I am A.
在程序中,我等到派生对象完全构造后再调用虚拟成员函数。但是,线程在对象完全构造之前启动。
如何确保虚拟通话?
答案 0 :(得分:9)
显示的代码显示竞争条件和未定义的行为。
在你的主要():
C c;
// ...
a->set_ready();
在set_ready()
返回后,执行线程立即离开main()
。这导致c
的 立即销毁 ,从超类C
开始,继续销毁B
,然后{{1 }}
A
在自动范围内声明。这意味着只要c
返回,它就会消失。加入合唱团看不见。它不复存在了。它不复存在。这是一个前对象。
您的main()
位于超类的析构函数 中。什么都不会阻止join()
被摧毁。当超类被破坏时,析构函数只会暂停并等待加入线程,但C
将立即开始销毁!
一旦C
超类被销毁,其虚拟方法就不再存在,并且调用虚函数将最终在基类中执行虚函数。
同时另一个执行线程正在等待互斥锁和条件变量。竞争条件是你不能保证其他执行线程会在父线程销毁C
之前被唤醒并开始执行,它会在发出条件变量信号后立即执行。
表示条件变量的所有信号都表明,无论执行线程在条件变量上旋转,执行线程都将开始执行。最终。该线程可以在一个非常负载的服务器上,在通过条件变量发出信号后,在几秒钟后开始执行。它的目标很久以前就消失了。它处于自动范围,C
将其销毁(或者,main()
子类已经被销毁,C
的析构函数正在等待加入线程。)
您正在观察的行为是在A
在接收到条件变量的信号并解锁之后C
在进行虚拟方法调用之前销毁std::thread
超类的父线程互斥。
这就是竞争条件。
此外,在销毁虚拟对象的同时执行虚拟方法调用已经不是首发。这是未定义的行为。即使执行线程在重写方法中结束,其对象也会被另一个线程同时销毁。无论你转向哪个方向,你都非常紧张。
经验教训:绑定std::thread
以执行this
对象中的某些内容是未定义行为的雷区。有办法正确地做到这一点,但这很难。
答案 1 :(得分:2)
这是最有可能的事件序列:
getname
,打印出“我是C!”因为它是C。main
开始返回。getname
,打印出“我是A!”因为它是一个A(对象的C和B部分现在已被破坏)。为了可靠地获得预期的行为,您需要等待打印线程退出,然后 }
的结束main
。
答案 2 :(得分:0)
其他答案是明确的,但并未显示可能的解决方法。这是带有其他变量并等待的相同程序:
#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();
}
I am C.
I am C.