简体   繁体   English

双重检查创建线程安全单例和无锁

[英]Double-checked creation of thread-safe singleton and lock-free

I wrote the following code creating singleton instance of my interface manager. 我编写了以下代码,以创建接口管理器的单例实例。

#include <intrin.h>
#pragma intrinsic(_ReadWriteBarrier)

boost::mutex global_interface_manager_creation_mutex;
interface_manager* global_interface_manager = NULL;

interface_manager* get_global_interface_manager() {
    interface_manager* volatile temp = global_interface_manager;
    _ReadWriteBarrier();
    if (temp == NULL) {
        boost::mutex::scoped_lock(global_interface_manager_creation_mutex);

        temp = global_interface_manager;

        if (temp == NULL) {
            temp = new interface_manager();
            _ReadWriteBarrier();
            global_interface_manager = temp; 
        }
    }

    return temp;
}

But I don't want to use the lock and memory barrier so change the code to: 但我不想使用锁定和内存屏障,因此将代码更改为:

interface_manager* get_global_interface_manager() {
    interface_manager* volatile temp = global_interface_manager;

    __assume(temp != NULL);
    if (temp == NULL) {
        temp = new interface_manager();
        if(NULL != ::InterlockedCompareExchangePointer((volatile PVOID *)&global_interface_manager, temp, NULL)) {
            delete temp;

            temp = global_interface_manager;
        }
    }

    return temp;
}

It seems like this code works well but I don't be sure and I really don't know how to test it is correct. 似乎这段代码运行良好,但我不确定,我真的不知道如何测试它是正确的。

My question would be: Is it really, really, really necessary to make a threadsafe singleton? 我的问题是:是否真的,真的,真的有必要制作线程安全的单身人士?

Singletons are debatable, but they do have their uses (and I guess discussing those would be going far off-topic). 单身人士是值得商bat的,但他们确实有其用途(我想讨论这些内容将远远超出主题)。

However, threadsafe singletons are something that is 99.99% of the time unnecessary and 99.99% of the time implemented wrong, too (even people who should know how to do it right have proven in the past that they got it wrong). 然而, 线程安全单例是99.99%的时间不必要的,99.99%的时间实现错误(甚至那些应该知道如何做正确的人在过去证明他们弄错了)。 So, I think that in this case "do you really need this" is a valid concern. 所以,我认为在这种情况下“你真的需要这个”是一个有效的问题。

If you create an instance of your singleton at application startup, for example from within main(), there will be only one thread. 如果在应用程序启动时(例如从main()内部)创建单例的实例,则将只有一个线程。 That can be as easy as calling get_global_interface_manager() once, or calling yourlibrary::init() which implicitly calls get(). 就像一次调用get_global_interface_manager()或调用隐式调用get()的yourlibrary :: init()一样容易。

Any concerns about thread-safety are irrelevant as soon as you do this, as there will be forcibly only one thread at this time. 一旦执行此操作,就不必担心线程安全,因为此时将仅强制一个线程。 And, you are guaranteed that it will work. 并且,您可以保证它会起作用。 No ifs and whens. 没有ifs和whens。

A lot of, if not all, libraries require you to call an init function at startup, so that is not an uncommon requirement, either. 很多(如果不是全部)库都要求您在启动时调用init函数,因此这也不是不常见的要求。

Have you looked into using pthread_once ? 你有没有考虑过使用pthread_once http://sourceware.org/pthreads-win32/manual/pthread_once.html http://sourceware.org/pthreads-win32/manual/pthread_once.html

This is the use case it was made for. 这是它的用例。

#include <stddef.h> // NULL
#include <pthread.h>

static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static interface_manager* m;

static void* init_interface_manager()
{
    m = new interface_manager;
    return NULL;
}

interface_manager* get_global_interface_manager()
{
    pthread_once(&once_control, &init_interface_manager);
    return m;
}

You could store the singleton as an atomic reference. 您可以将单例存储为原子引用。 After instantiating, CAS to set the reference. 实例化后,CAS设置引用。 This will not guarantee that two copies don't get instantiated, only that the same one will always be returned from inst. 这不能保证不会实例化两个副本,而只能保证总是从inst返回相同的副本。 Since standard C doesn't have atomic instructions I can show it in Java: 由于标准C没有原子指令,我可以在Java中显示它:

class Foo
{
    static AtomicReference<Foo> foo = new AtomicReference<>();

    public static Foo inst()
    {
        Foo atomic = foo.get();
        if(atomic != null)
            return atomic;
        else
        {
            Foo newFoo = new Foo();
            //newFoo will only be set if no other thread has set it
            foo.compareAndSet(null, newFoo);
            //if the above CAS failed, foo.get would be a different object
            return foo.get();
        }
    }
}

One of the difficult parts of multi-threaded programming is that something might appear to work 99.9% of the time, then fail miserably. 多线程编程的一个难点是,某些东西看起来可能有99.9%的时间工作,然后惨遭失败。

In your case, there's nothing to prevent two threads from getting NULL back from the global pointer and both allocating new singletons. 在你的情况下,没有什么可以防止两个线程从全局指针返回NULL并且都分配新的单例。 One will get deleted, but you'll still pass it back as the return value from the function. 一个将被删除,但您仍然会将其作为函数的返回值传回。

I even had trouble convincing myself that my own analysis was correct. 我甚至无法说服自己,我自己的分析是正确的。

You could fix it easily by returning global_interface_manager rather than temp . 您可以通过返回global_interface_manager而不是temp来轻松修复它。 There's still the possibility of creating an interface_manager that you turn around and delete, but I assume that was your intention. 仍然有可能创建一个你转身并删除的interface_manager ,但我认为这是你的意图。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM