【问题标题】:nofify_all() crashes when the program is closed程序关闭时 nofify_all() 崩溃
【发布时间】:2019-02-28 15:51:10
【问题描述】:

我有一个非常简单的 C++ 程序,如下所示。 A、B 和 C 类位于 DLL 中。当我关闭此应用程序时,它有时会在条件变量上调用 notify_all() 时崩溃。谁能告诉我原因?

我已经检查了很多关于 SO 的 Q/A,但没有一个能解决我的问题。我在 Windows 7 和 VS2013 上工作。

    class B;

    class C
    {
    public:
        C(const std::weak_ptr<B>& b) : mB(b)
        {
        }
        virtual ~C() 
        {
        }

        void run()
        {
            while (true)
            {
                std::unique_lock<std::mutex> uLock(mMutex);

                // Wait until some event is happening
                mCondition.wait_for(uLock, std::chrono::seconds(300));

                if (!mStop)
                {
                    //do something here
                }
                else
                {
                    return;
                }
            }
        }


        void start()
        {
            mThread = std::thread(&C::run, this);
        }

        void stop()
        {
            mStop = false;
        }

        void notify()
        {
            mCondition.notify_all();
        }

        void join()
        {
            if (mThread.joinable())
            {
                mThread.join();
            }
        }

        private:
            std::atomic<bool> mStop;
            std::condition_variable mCondition;
            std::mutex mMutex;
            std::thread mThread;
            std::weak_ptr<B> mB;
        };


    class B : public std::enable_shared_from_this<B>
    {
    public:
        B() {}
        ~B()
        {
            if (mC)
            {
                mC->stop();
                mC->notify();
                mC->join();
            }
        }

        // basic methods
        void init()
        {
            mC = std::unique_ptr<C>(new C(shared_from_this()));
            mC->start();
        }

    private:
        std::unique_ptr<C> mC;
    };

    class A
    {
    public:
        ~A(){}

        void init() { pImpl->init(); }

        static std::shared_ptr<A> getInstance(){
            static std::shared_ptr<A> instance(new A);
            return instance;
        }

    private:
        A() : pImpl(std::make_shared<B>()){}
        std::shared_ptr<B> pImpl;
    };


    void main()
    {
        std::shared_ptr<A> a = A::getInstance();
        a->init();

        int x;
        std::cin >> x;
    }

编辑 1: 如果我将 B 的析构函数中的代码放在不同的函数中(例如 clean())并从 main() 调用它(在 A 中使用 clean() 方法)不会崩溃正在发生。

【问题讨论】:

    标签: c++ multithreading c++11 condition-variable


    【解决方案1】:

    代码错过了条件变量通知,因为:

    1. stop_ = true 期间不持有互斥锁(它应该是true,而不是false)。 stop_ 必须在持有互斥体时读取和修改,并且它不需要是原子的。当人们将原子与互斥锁和条件变量一起使用时,这是导致竞争条件的常见原因。
    2. 条件变量等待代码在等待之前不检查条件。

    修复:

    class B;
    
    class C
    {
    public:
        C(const std::weak_ptr<B>& b) : mB(b) {}
        ~C() { stop(); }
    
        void run()
        {
            while (true) {
                std::unique_lock<std::mutex> uLock(mMutex);
                while(!mStop /* && !other_real_condition */)
                    mCondition.wait_for(uLock, std::chrono::seconds(300));
                if(mStop)
                    return;
                // other_real_condition is true, process it.
            }
        }
    
    
        void start()
        {
            mThread = std::thread(&C::run, this);
        }
    
        void stop()
        {
            {
                std::unique_lock<std::mutex> uLock(mMutex);
                mStop = true;
            }
            mCondition.notify_all();
            if (mThread.joinable())
                mThread.join();
        }
    
        private:
            bool mStop = false; // <--- do not forget to initialize
            std::condition_variable mCondition;
            std::mutex mMutex;
            std::thread mThread;
            std::weak_ptr<B> mB;
        };
    
    
    class B : public std::enable_shared_from_this<B>
    {
    public:
    
        // basic methods
        void init()
        {
            mC = std::unique_ptr<C>(new C(shared_from_this()));
            mC->start();
        }
    
    private:
        std::unique_ptr<C> mC;
    };
    

    如果您设置 mStop 而不持有互斥锁,则会发生以下情况:

    | Thread 1              | Thread 2            |
    | mStop = true          |                     |
    | mCondition.notify_all |                     |
    |                       | mMutex.lock         |
    |                       | mCondition.wait_for |
    

    尽管设置了mStop,但上述线程 2 丢失通知并等待。

    在更新共享状态时锁定互斥锁可修复竞争条件:

    | Thread 1                | Thread 2               |
    | mMutex.lock             |                        |
    | mStop = true            |                        |
    | mCondition.notify_all   |                        |
    | mMutex.unlock           |                        |
    |                         | mMutex.lock            |
    |                         | mStop == true, no wait |
    

    在等待条件变量时,必须在持有互斥锁的同时修改和读取共享状态,否则条件通知会丢失并可能导致死锁(在没有超时的情况下等待)。这就是为什么不需要将原子与互斥体和条件变量一起使用的原因,您要么使用原子,要么使用互斥体和条件变量,但不能同时使用。

    【讨论】:

    • 为什么我应该把 wait_for 函数放在一个 while 循环上?实际上,就我而言,我没有 real_condition。我只想每 300 秒做一些代码,如果另一个线程想停止,我也想停止这个线程。
    • @hsalimi 有条件变量虚假唤醒。条件变量没有状态,因此通知可能会丢失。 en.wikipedia.org/wiki/Spurious_wakeup
    • 但在我的情况下,如果我在 while 循环中使用 wait_for(...),5 分钟后,线程将唤醒并再次进入睡眠状态。不?我不能有一个 real_condition 而不是超时。
    • @hsalimi 为您添加了详细信息。
    • 谢谢马克西姆。现在对我来说很清楚了。最后一个问题是,在您的示例代码中,您在将 mStop 设置为 true 之后立即释放 mMutex,但在之后描绘的线程泳道中,您在调用 notify_all 后释放此互斥锁。哪一个应该是正确的答案?调用 notify_all 时是否需要持有锁?
    【解决方案2】:

    这似乎是一个 CRT 错误 (https://stackoverflow.com/a/50525968/896012)。该问题不会发生在新版本的 Windows 上,即 Windows 10。为了修复 Windows 7 上的崩溃,我只是删除了 condition_variable 并使用了简单的睡眠,而在退出程序时我只是分离了线程。虽然这不是一个干净的方法,但我认为这是避免崩溃的唯一方法。如果有人找到更好的答案,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-06
      • 2013-12-12
      • 1970-01-01
      • 1970-01-01
      • 2017-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多