c++ - 线程内的虚拟调用忽略派生类

标签 c++ multithreading thread-safety

在下面的程序中,我有一个线程内的虚拟调用:

#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.

但是instead it prints :

I am C.
I am A.

在程序中,我等到派生对象完全构造完成后才调用虚拟成员函数。然而,线程在对象完全构造之前启动。

虚拟通话如何放心?

最佳答案

显示的代码表现出竞争条件和未定义的行为。

在你的 main() 中:

C c;

// ...

a->set_ready();

set_ready() 返回后,执行线程立即离开 main()。这导致 c立即销毁,从父类(super class) C 开始,并继续销毁 B ,然后是 A

c 在自动范围内声明。这意味着一旦 main() 返回,它就消失了。加入了无形的合唱团。它已经不复存在了。它不复存在了。这是一个前物体。

您的join() 在父类(super class)的析构函数中。没有什么可以阻止 C 被销毁。当 superclass 被销毁时,析构函数只会暂停并等待加入线程,但 C 将立即开始被销毁!

一旦C父类(super class)被销毁,它的虚方法就不存在了,调用虚函数最终会执行基类中的虚函数。

与此同时,另一个执行线程正在等待互斥锁和条件变量。竞争条件是您无法保证其他执行线程会在父线程销毁 C 之前唤醒并开始执行,父线程会在发出条件变量信号后立即销毁。

发出条件变量信号的所有信息是,无论执行线程在条件变量上旋转,该执行线程都会开始执行。最终。在负载非常大的服务器上,该线程可以在通过条件变量发出信号后几秒钟后开始执行。它的对象很久以前就不见了。它在自动范围内,并且 main() 销毁了它(或者更确切地说,C 子类已经被销毁,并且 A 的析构函数正在等待加入线程)。

您观察到的行为是父线程设法销毁 C 父类(super class),然后 std::thread 在收到来自条件变量的信号,并解锁其互斥量。

这就是竞争条件。

此外,在销毁虚拟对象的同时执行虚拟方法调用已经是行不通的。这是未定义的行为。即使执行线程在重写的方法中结束,它的对象也会同时被另一个线程销毁。到那时,无论你转向哪个方向,你都完蛋了。

经验教训:装配 std::thread 以在 this 对象中执行某些操作是未定义行为的雷区。有很多方法可以正确地做到这一点,但这很难。

关于c++ - 线程内的虚拟调用忽略派生类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41051306/

相关文章:

c++ - 我可以使 GetOpenFileName 返回路径长于 MAX_PATH 吗?

c++ - 让编译器检查数组初始值设定项的数量

c++ - 线程池共享资源锁定问题

java - 设置正在运行的线程的属性

java - 放置和弹出值堆栈的 Struts 2 拦截器是线程安全的吗?

c++ - 如何在不暂停执行的情况下获取 X 窗口系统中的事件?

c++ - Armadillo 是否支持 bool 和 8 位类型的矩阵和立方体?

multithreading - macosx 线程显式标记为已删除

c - 是否有官方文档将读/写函数标记为线程安全函数?

java - 代码从顺序到线程