如何设计一个需要在其析构函数中加入一个线程的基类,该线程对同一个类实例进行操作?

Hol*_*Cat 8 c++ polymorphism multithreading race-condition stdthread

我刚刚得到了一个有趣的比赛条件。考虑以下类:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

class A
{
    std::thread th;
    std::atomic_bool stop = false;

  public:
    A() = default;
    A(const A &) = delete;
    A &operator=(const A &) = delete;

    ~A()
    {
        stop.store(true);
        th.join();
    }

    virtual void Step() = 0;

    void Start()
    {
        th = std::thread([this]
        {
            while (true)
            {
                if (stop.load())
                    return;

                // Just to help reproduce the race condition.
                std::this_thread::sleep_for(std::chrono::milliseconds(50));

                Step();
            }
        });
    }
};

struct B : A
{
    void Step() override
    {
        std::cout << "Step!" << std::endl;
    }
};

int main()
{
    B b;

    b.Start();

    // Just to help reproduce the race condition.
    std::this_thread::sleep_for(std::chrono::milliseconds(110));
}
Run Code Online (Sandbox Code Playgroud)

这会由于纯虚函数调用而崩溃(如果我们忽略数据竞争 UB)。

就在我们进入 的主体之前~A(),在我们停止线程之前,vtable 指针更改为指向A的 vtable,因此下一次调用会Step()崩溃,现在是纯虚拟调用。

这可以通过停止 中的线程来解决~B(),但这意味着每个派生类都必须记住这样做,否则会发生不好的事情。

我希望我能设计A自己来解决这个问题。是否可以?