为什么这个C ++静态单例永远不会停止?

时间:2013-06-13 17:16:17

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

我在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();
};

singleton.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;
}

我已经知道的事情:

  • 线程终止
  • 主线程卡在连接中
  • 它与静态有关,如果我将构造函数设为public并在main()中创建一个Singleton实例,它将正确终止。

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

6 个答案:

答案 0 :(得分:22)

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

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

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

答案 1 :(得分:7)

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

答案 2 :(得分: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();
};

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

答案 3 :(得分:4)

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

  

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

答案 4 :(得分:1)

此死锁错误与

中的相同

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

并未在Visual Studio 2013中修复。

答案 5 :(得分:0)

至少在此特定示例中,它似乎已在Visual Studio 2015及更高版本中修复。