如何在 Windows 中创建线程安全的单例模式?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/164496/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 11:22:38  来源:igfitidea点击:

How can I create a thread-safe singleton pattern in Windows?

c++windowssingleton

提问by Mark Ingram

I've been reading about thread-safe singleton patterns here:

我一直在这里阅读有关线程安全的单例模式:

http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29

http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29

And it says at the bottom that the only safe way is to use pthread_once - which isn't available on Windows.

它在底部说唯一安全的方法是使用 pthread_once - 这在 Windows 上不可用。

Is that the onlyway of guaranteeing thread safe initialisation?

这是保证线程安全初始化的唯一方法吗?

I've read this thread on SO:

我已经在 SO 上阅读了这个线程:

Thread safe lazy construction of a singleton in C++

C ++中单例的线程安全延迟构造

And seems to hint at an atomic OS level swap and compare function, which I assume on Windows is:

并且似乎暗示了一个原子操作系统级别的交换和比较功能,我在 Windows 上假设它是:

http://msdn.microsoft.com/en-us/library/ms683568.aspx

http://msdn.microsoft.com/en-us/library/ms683568.aspx

Can this do what I want?

这可以做我想要的吗?

Edit:I would like lazy initialisation and for there to only ever be one instance of the class.

编辑:我想要延迟初始化,并且只有一个类的实例。

Someone on another site mentioned using a global inside a namespace (and he described a singleton as an anti-pattern) - how can it be an "anti-pattern"?

另一个站点上的某人提到在命名空间内使用全局(他将单例描述为反模式) - 它怎么可能是“反模式”?

Accepted Answer:
I've accepted Josh's answeras I'm using Visual Studio 2008 - NB: For future readers, if you aren't using this compiler (or 2005) - Don't use the accepted answer!!

接受的答案:
我已经接受了Josh 的回答,因为我使用的是 Visual Studio 2008 - 注意:对于未来的读者,如果你没有使用这个编译器(或 2005) - 不要使用接受的答案!!

Edit:The code works fine except the return statement - I get an error: error C2440: 'return' : cannot convert from 'volatile Singleton *' to 'Singleton *'. Should I modify the return value to be volatile Singleton *?

编辑:除 return 语句外,代码工作正常 - 我收到一个错误:错误 C2440:'return':无法从 'volatile Singleton *' 转换为 'Singleton *'。我是否应该将返回值修改为 volatile Singleton *?

Edit:Apparently const_cast<> will remove the volatile qualifier. Thanks again to Josh.

编辑:显然 const_cast<> 将删除 volatile 限定符。再次感谢乔希。

采纳答案by Eclipse

If you are are using Visual C++ 2005/2008 you can use the double checked locking pattern, since "volatile variables behave as fences". This is the most efficient way to implement a lazy-initialized singleton.

如果您使用的是 Visual C++ 2005/2008,您可以使用双重检查锁定模式,因为“ volatile 变量表现为栅栏”。这是实现延迟初始化单例的最有效方法。

From MSDN Magazine:

来自MSDN 杂志:

Singleton* GetSingleton()
{
    volatile static Singleton* pSingleton = 0;

    if (pSingleton == NULL)
    {
        EnterCriticalSection(&cs);

        if (pSingleton == NULL)
        {
            try
            {
                pSingleton = new Singleton();
            }
            catch (...)
            {
                // Something went wrong.
            }
        }

        LeaveCriticalSection(&cs);
    }

    return const_cast<Singleton*>(pSingleton);
}

Whenever you need access to the singleton, just call GetSingleton(). The first time it is called, the static pointer will be initialized. After it's initialized, the NULL check will prevent locking for just reading the pointer.

每当您需要访问单例时,只需调用 GetSingleton()。第一次调用时,静态指针将被初始化。初始化后,NULL 检查将阻止仅读取指针的锁定。

DO NOTuse this on just any compiler, as it's not portable. The standard makes no guarantees on how this will work. Visual C++ 2005 explicitly adds to the semantics of volatile to make this possible.

不要在任何编译器上使用它,因为它不可移植。该标准不保证这将如何工作。Visual C++ 2005 显式地添加了 volatile 的语义以使这成为可能。

You'll have to declare and initialize the CRITICAL SECTIONelsewhere in code. But that initialization is cheap, so lazy initialization is usually not important.

您必须在代码的其他地方声明和初始化 CRITICAL SECTION。但是这种初始化很便宜,所以惰性初始化通常并不重要。

回答by Matthew Murdoch

A simple way to guarantee cross-platform thread safe initialization of a singletonis to perform it explicitly (via a call to a static member function on the singleton) in the main thread of your application beforeyour application starts any other threads (or at least any other threads that will access the singleton).

保证单例跨平台线程安全初始化的一种简单方法是应用程序启动任何其他线程(或至少将访问单例的任何其他线程)。

Ensuring thread safe access to the singleton is then achieved in the usual way with mutexes/critical sections.

然后通过互斥锁/临界区以通常的方式确保线程安全地访问单例。

Lazy initializationcan also be achieved using a similar mechanism. The usual problem encountered with this is that the mutex required to provide thread-safety is often initialized in the singleton itself which just pushes the thread-safety issue to initialization of the mutex/critical section. One way to overcome this issue is to create and initialize a mutex/critical section in the main thread of your application then pass it to the singleton via a call to a static member function. The heavyweight initialization of the singleton can then occur in a thread-safe manner using this pre-initialized mutex/critical section. For example:

延迟初始化也可以使用类似的机制来实现。遇到的常见问题是提供线程安全所需的互斥锁通常在单例本身中初始化,这只会将线程安全问题推到互斥锁/临界区的初始化中。解决此问题的一种方法是在应用程序的主线程中创建和初始化互斥锁/临界区,然后通过调用静态成员函数将其传递给单例。然后可以使用这个预初始化的互斥体/临界区以线程安全的方式进行单例的重量级初始化。例如:

// A critical section guard - create on the stack to provide 
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
    private:
        LPCRITICAL_SECTION CriticalSection;

    public:
        Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
            EnterCriticalSection(CriticalSection);
        }

        ~Guard() {
            LeaveCriticalSection(CriticalSection);
        }
};

// A thread-safe singleton
class Singleton {
    private:
        static Singleton* Instance;
        static CRITICAL_SECTION InitLock;
        CRITICIAL_SECTION InstanceLock;

        Singleton() {
            // Time consuming initialization here ...

            InitializeCriticalSection(&InstanceLock);
        }

        ~Singleton() {
            DeleteCriticalSection(&InstanceLock);
        }

    public:
        // Not thread-safe - to be called from the main application thread
        static void Create() {
            InitializeCriticalSection(&InitLock);
            Instance = NULL;
        }

        // Not thread-safe - to be called from the main application thread
        static void Destroy() {
            delete Instance;
            DeleteCriticalSection(&InitLock);
        }

        // Thread-safe lazy initializer
        static Singleton* GetInstance() {
            Guard(&InitLock);

            if (Instance == NULL) {
                Instance = new Singleton;
            }

            return Instance;
        }

        // Thread-safe operation
        void doThreadSafeOperation() {
            Guard(&InstanceLock);

            // Perform thread-safe operation
        }
};

However, there are good reasons to avoid the use of singletons altogether (and why they are sometimes referred to as an anti-pattern):

然而,有充分的理由完全避免使用单例(以及为什么它们有时被称为反模式):

  • They are essentially glorified global variables
  • They can lead to high coupling between disparate parts of an application
  • They can make unit testing more complicated or impossible (due to the difficultly in swapping real singletons with fake implementations)
  • 它们本质上是美化的全局变量
  • 它们可能导致应用程序不同部分之间的高度耦合
  • 它们会使单元测试变得更加复杂或不可能(因为很难将真正的单例与假实现交换)

An alternative is to make use of a 'logical singleton' whereby you create and initialise a single instance of a class in the main thread and pass it to the objects which require it. This approach can become unwieldy where there are many objects which you want to create as singletons. In this case the disparate objects can be bundled into a single 'Context' object which is then passed around where necessary.

另一种方法是利用“逻辑单例”,您可以在主线程中创建和初始化类的单个实例,并将其传递给需要它的对象。如果您想将许多对象创建为单例,这种方法可能会变得笨拙。在这种情况下,可以将不同的对象捆绑到一个“上下文”对象中,然后在必要时传递该对象。

回答by TripShock

While I like the accepted solution, I just found another promising lead and thought I should share it here: One-Time Initialization (Windows)

虽然我喜欢接受的解决方案,但我刚刚找到了另一个有前途的线索,并认为我应该在这里分享它:一次性初始化(Windows)

回答by Henk

You can use an OS primitive such as mutex or critical section to ensure thread safe initialization however this will incur an overhead each time your singleton pointer is accessed (due to acquiring a lock). It's also non portable.

您可以使用诸如互斥锁或临界区之类的操作系统原语来确保线程安全初始化,但是每次访问单例指针时都会产生开销(由于获取锁)。它也是不可移植的。

回答by JaredPar

There is one clarifying point you need to consider for this question. Do you require ...

对于这个问题,您需要考虑一个澄清点。你需要...

  1. That one and only one instance of a class is ever actually created
  2. Many instances of a class can be created but there should only be one true definitive instance of the class
  1. 一个并且只有一个类的实例被实际创建
  2. 可以创建一个类的许多实例,但该类应该只有一个真正的明确实例

There are many samples on the web to implement these patterns in C++. Here's a Code Project Sample

网络上有许多示例可以用 C++ 实现这些模式。这是一个代码项目示例

回答by Eric

The following explains how to do it in C#, but the exact same concept applies to any programming language that would support the singleton pattern

下面解释了如何在 C# 中做到这一点,但完全相同的概念适用于任何支持单例模式的编程语言

http://www.yoda.arachsys.com/csharp/singleton.html

http://www.yoda.arachsys.com/csharp/singleton.html

What you need to decide is wheter you want lazy initialization or not. Lazy initialization means that the object contained inside the singleton is created on the first call to it ex :

您需要决定的是是否需要延迟初始化。延迟初始化意味着包含在单例中的对象是在第一次调用它时创建的 ex :

MySingleton::getInstance()->doWork();

if that call isnt made until later on, there is a danger of a race condition between the threads as explained in the article. However, if you put

如果该调用直到稍后才进行,则线程之间存在竞争条件的危险,如文章中所述。但是,如果你把

MySingleton::getInstance()->initSingleton();

at the very beginning of your code where you assume it would be thread safe, then you are no longer lazy initializing, you will require "some" more processing power when your application starts. However it will solve a lot of headaches about race conditions if you do so.

在您的代码的最开始,您假设它是线程安全的,然后您不再是懒惰的初始化,当您的应用程序启动时,您将需要“一些”更多的处理能力。然而,如果你这样做,它将解决很多关于竞态条件的头痛问题。

回答by mmocny

If you are looking for a more portable, and easier solution, you could turn to boost.

如果您正在寻找更便携、更简单的解决方案,您可以转向 boost。

boost::call_oncecan be used for thread safe initialization.

boost::call_once可用于线程安全初始化。

Its pretty simple to use, and will be part of the next C++0x standard.

它使用起来非常简单,并将成为下一个 C++0x 标准的一部分。

回答by zhaorufei

The question does not require the singleton is lazy-constructed or not. Since many answers assume that, I assume that for the first phrase discuss:

问题不要求单例是否是惰性构造的。由于许多答案都假设,我假设第一个短语讨论:

Given the fact that the language itself is not thread-awareness, and plus the optimization technique, writing a portable reliable c++ singleton is very hard (if not impossible), see "C++ and the Perils of Double-Checked Locking" by Scott Meyers and Andrei Alexandrescu.

鉴于语言本身不是线程感知的,再加上优化技术,编写一个可移植的可靠的 c++ 单例是非常困难的(如果不是不可能的话),请参阅Scott Meyers 的“ C++ 和双重检查锁定的危险”和安德烈亚历山德雷斯库。

I've seen many of the answer resort to sync object on windows platform by using CriticalSection, but CriticalSection is only thread-safe when all the threads is running on one single processor, today it's probably not true.

我已经看到许多答案通过使用 CriticalSection 在 Windows 平台上使用同步对象,但是当所有线程都在一个处理器上运行时,CriticalSection 只是线程安全的,今天它可能不是真的。

MSDN cite: "The threads of a single process can use a critical section object for mutual-exclusion synchronization. ".

MSDN 引用:“单个进程的线程可以使用临界区对象进行互斥同步。”。

And http://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

clearify it further:

进一步澄清:

A critical section object provides synchronization similar to that provided by a mutex object, except that a critical section can be used only by the threads of a single process.

临界区对象提供类似于互斥对象提供的同步,除了临界区只能由单个进程的线程使用。

Now, if "lazy-constructed" is not a requirement, the following solution is both cross-module safe and thread-safe, and even portable:

现在,如果“懒惰构造”不是必需的,那么以下解决方案既是跨模块安全又是线程安全的,甚至是可移植的:

struct X { };

X * get_X_Instance()
{
    static X x;
    return &x;
}
extern int X_singleton_helper = (get_X_instance(), 1);

It's cross-module-safe because we use locally-scoped static object instead of file/namespace scoped global object.

它是跨模块安全的,因为我们使用本地范围的静态对象而不是文件/命名空间范围的全局对象。

It's thread-safe because: X_singleton_helper must be assigned to the correct value before entering main or DllMain It's not lazy-constructed also because of this fact), in this expression the comma is an operator, not punctuation.

它是线程安全的,因为: X_singleton_helper 必须在进入 main 或 DllMain 之前分配给正确的值,也因为这个事实,它不是惰性构造的),在这个表达式中,逗号是一个运算符,而不是标点符号。

Explicitly use "extern" here to prevent compiler optimize it out(Concerns about Scott Meyers article, the big enemy is optimizer.), and also make static-analyze tool such as pc-lint keep silent. "Before main/DllMain" is Scott meyer called "single-threaded startup part" in "Effective C++ 3rd" item 4.

在这里显式使用“extern”来防止编译器优化它(关注 Scott Meyers 的文章,最大的敌人是优化器。),并使 pc-lint 等静态分析工具保持沉默。“在 main/DllMain 之前”是 Scott meyer 在“Effective C++ 3rd”第 4 项中称为“单线程启动部分”的部分。

However, I'm not very sure about whether compiler is allowed to optimize the call the get_X_instance() out according to the language standard, please comment.

但是,我不太确定是否允许编译器根据语言标准优化调用 get_X_instance() ,请发表评论。

回答by 1800 INFORMATION

There are many ways to do thread safe Singleton* initialization on windows. In fact some of them are even cross-platform. In the SO thread that you linked to, they were looking for a Singleton that is lazily constructed in C, which is a bit more specific, and can be a bit trickier to do right, given the intricacies of the memory model you are working under.

有很多方法可以在 Windows 上进行线程安全的 Singleton* 初始化。事实上,其中一些甚至是跨平台的。在您链接到的 SO 线程中,他们正在寻找一个用 C 懒惰构建的单例,它更具体一点,并且考虑到您正在工作的内存模型的复杂性,做对可能有点棘手.

  • which you should never use
  • 你永远不应该使用的