【问题标题】:Why does this C++ static singleton never stop?为什么这个 C++ 静态单例永远不会停止?
【发布时间】:2013-06-10 04:45:44
【问题描述】:

我已经在 C++ 中实现了一个单例(静态版本)。我知道关于这种模式和潜在线程安全问题的所有争议,但我很好奇为什么这个确切的实现不会停止。程序从不退出,最后一直处于死锁状态。

singleton.h:

#pragma once
#include <thread>
#include <atomic>

class Singleton
{
public:
    static Singleton& getInstance();

private:
    std::thread mThread;
    std::atomic_bool mRun;

    Singleton();
    ~Singleton();
    void threadFoo();
};

单例.cpp

#include "singleton.h"

Singleton& Singleton::getInstance()
{
    static Singleton instance;
    return instance;
} 

Singleton::Singleton()
{
    mRun.store(true);
    mThread = std::thread(&Singleton::threadFoo, this);
}

Singleton::~Singleton()
{
    mRun.store(false);

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

void Singleton::threadFoo()
{
    while(mRun)
    {
    }
}

main.cpp

#include "singleton.h"

int main()
{
    Singleton::getInstance();
    return 0;
}

我已经知道的:

  • 线程终止
  • 主线程卡在join中
  • 它与静态有关,如果我公开构造函数并在 main() 中创建 Singleton 实例,它将正确终止。

使用 Visual Studio 2012。感谢您的建议。

【问题讨论】:

  • 混合全局关闭和运行线程充其量是棘手的。目前我找不到任何标准参考,但我怀疑您正在尝试做的事情(让线程比 main 的结尾更有效)实际上是未定义的行为。
  • 可能是因为静态实例比main()寿命长。
  • 这个程序在使用 gcc (g++-4.8 test.cpp -O2 -pthread -std=c++0x) 编译时可以正常启动和结束。我不知道这对任何人是否有用
  • @Xcessity:承认你知道它,你只是想对 MS 错误进行攻击......呃我的意思是功能。 :)
  • 静态变量在 main() 退出后被销毁。很可能有某种隐藏的内部线程列表 - 在其他地方,可能也是静态的,一旦 main() 完成,它也会被清理。在您的情况下,这很可能发生在您的 Singleton 被破坏并破坏线程行为之前。请注意,我实际上并没有对此进行测试,这只是我能想到的最有可能的情况。另外,假设你有专业版的 VS 和 crt 源代码,你应该可以跳出 main(),看看调试器会发生什么。

标签: c++ multithreading c++11 static singleton


【解决方案1】:

在主线程上,main() 终止后,CRT 获取退出锁并调用您的静态实例析构函数,该析构函数等待您的后台线程退出。

在后台线程上,在您的线程函数终止后,CRT 会尝试获取退出锁以执行一些线程终止工作。这将永远阻塞,因为退出锁由等待 this 线程退出的主线程持有。

这是一个由 CRT 实现引起的简单死锁。底线是您不能在 Windows 上的静态实例析构函数中等待线程终止。

【讨论】:

    【解决方案2】:

    我已将其追溯到mlock.c 内的void __cdecl _lock(int locknum)。当main() 结束时,主线程去那里并进入临界区EnterCriticalSection( _locktable[locknum].lock );。然后调用 Singleton 析构函数,另一个线程尝试进入相同的临界区,但不能,因此它开始等待主线程离开临界区。反过来,主线程等待另一个线程。所以我猜这是一个错误。

    【讨论】:

      【解决方案3】:

      参见标准中的 [basic.start.term]:

      如果有一个标准库对象或函数的使用不是 在以前不会发生的信号处理程序(18.10)中允许 (1.10) 用静态存储完成对象的销毁 std::atexit 注册函数 (18.5) 的持续时间和执行, 程序具有未定义的行为。 [注:如果有使用对象 静态存储持续时间不会发生在对象之前 破坏,程序具有未定义的行为。终止每个 在调用 std::exit 或从 main 退出之前的线程就足够了, 但不是必需的,以满足这些要求。这些要求 允许线程管理器作为静态存储持续时间对象。 ——尾注]

      【讨论】:

        【解决方案4】:

        好的,谢谢大家的提示。显然,这种模式实现会导致 VC++ 出现死锁。

        在做了一些进一步的研究后,我发现了这个基于 C++11 机制的实现,它在 VC++ 中运行。

        singleton.h

        #pragma once
        #include <thread>
        #include <atomic>
        #include <memory>
        #include <mutex>
        
        
        class Singleton
        {
        public:
            static Singleton& getInstance();
            virtual ~Singleton();
        
        private:
            static std::unique_ptr<Singleton> mInstance;
            static std::once_flag mOnceFlag;
            std::thread mThread;
            std::atomic_bool mRun;
        
            Singleton();
        
            void threadFoo();
        };
        

        单例.cpp

        #include "singleton.h"
        
        std::unique_ptr<Singleton> Singleton::mInstance = nullptr;
        std::once_flag Singleton::mOnceFlag;
        
        
        Singleton& Singleton::getInstance()
        {
            std::call_once(mOnceFlag, [] { mInstance.reset(new Singleton); });
            return *mInstance.get();
        }
        
        
        Singleton::Singleton()
        {
            mRun.store(true);
            mThread = std::thread(&Singleton::threadFoo, this);
        }
        
        Singleton::~Singleton()
        { 
            mRun.store(false);
        
            if(mThread.joinable())
                mThread.join();
        }
        
        void Singleton::threadFoo()
        {
            while(mRun.load())
            {
            }
        }
        

        更新

        微软似乎意识到了这个问题。在 VC++ 论坛中,一位名为“dlafleur”的用户报告了这篇文章: https://connect.microsoft.com/VisualStudio/feedback/details/747145

        【讨论】:

          【解决方案5】:

          这个死锁bug和

          中的一样

          std::thread::join() hangs if called after main() exits when using VS2012 RC

          它在 Visual Studio 2013 中未修复。

          【讨论】:

            【解决方案6】:

            似乎在 Visual Studio 2015 及更高版本中已修复,至少对于这个特定示例而言。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-11-28
              • 1970-01-01
              • 2015-12-06
              • 2015-01-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-11-26
              相关资源
              最近更新 更多