簡體   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