繁体   English   中英

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

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

我编写了以下代码,以创建接口管理器的单例实例。

#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;
}

但我不想使用锁定和内存屏障,因此将代码更改为:

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

似乎这段代码运行良好,但我不确定,我真的不知道如何测试它是正确的。

我的问题是:是否真的,真的,真的有必要制作线程安全的单身人士?

单身人士是值得商bat的,但他们确实有其用途(我想讨论这些内容将远远超出主题)。

然而, 线程安全单例是99.99%的时间不必要的,99.99%的时间实现错误(甚至那些应该知道如何做正确的人在过去证明他们弄错了)。 所以,我认为在这种情况下“你真的需要这个”是一个有效的问题。

如果在应用程序启动时(例如从main()内部)创建单例的实例,则将只有一个线程。 就像一次调用get_global_interface_manager()或调用隐式调用get()的yourlibrary :: init()一样容易。

一旦执行此操作,就不必担心线程安全,因为此时将仅强制一个线程。 并且,您可以保证它会起作用。 没有ifs和whens。

很多(如果不是全部)库都要求您在启动时调用init函数,因此这也不是不常见的要求。

你有没有考虑过使用pthread_once http://sourceware.org/pthreads-win32/manual/pthread_once.html

这是它的用例。

#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;
}

您可以将单例存储为原子引用。 实例化后,CAS设置引用。 这不能保证不会实例化两个副本,而只能保证总是从inst返回相同的副本。 由于标准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();
        }
    }
}

多线程编程的一个难点是,某些东西看起来可能有99.9%的时间工作,然后惨遭失败。

在你的情况下,没有什么可以防止两个线程从全局指针返回NULL并且都分配新的单例。 一个将被删除,但您仍然会将其作为函数的返回值传回。

我甚至无法说服自己,我自己的分析是正确的。

您可以通过返回global_interface_manager而不是temp来轻松修复它。 仍然有可能创建一个你转身并删除的interface_manager ,但我认为这是你的意图。

暂无
暂无

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

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