簡體   English   中英

C ++多線程單例-這段代碼有什么問題

[英]c++ multithreaded singleton - anything wrong with this code

template <typename T, typename Lock>
class Singleton
{
public:
static T * getInstance()
{
    if (obj == 0)
    {
        lock.lock();
        if (obj == 0)
        {
            obj = new T;
        }
        lock.unlock();
    }
    return obj;
}

void deleteInstance()
{
   delete obj;
   obj = 0;
}

private:
volatile static T * obj = 0;
static Lock lock; // some sort of mutex
Singleton();
~Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
};

obj是否必須是易失的? 如果第一個線程創建了T實例,並說第二個線程在此之前已經在緩存中加載了obj,那么現代處理器的緩存是否會失效,或者第二個線程可能使用obj的0值並創建T第二次? 假設兩個線程都在不同的內核中運行。

另外,請讓我知道使用靜態T *代替靜態T作為單例數據時可能遇到的任何問題。

getInstance()方法使用雙重檢查鎖定 雙重檢查鎖定本質上是危險的,因為根據編譯器如何為該行生成代碼,可能會引入競爭條件:

obj = new T;

該行主要包括以下三個步驟:

  1. 分配sizeof (T)個字節。
  2. 在分配的空間中構造一個T對象。
  3. 將一個指針分配給obj分配的空間。

問題是,不需要編譯器來生成按順序執行這些步驟的代碼。 例如,它可以分配sizeof (T)個字節,然后分配obj然后就地構造T 在那種情況下,存在一個競爭條件,其中兩個不同的線程可以構造一個新的T對象。 同樣,兩個不同的線程可以嘗試在同一位置構造T對象。

請參閱Scott Meyers和Andrei Alexandrescu 撰寫的C ++和雙重檢查鎖定的風險

至於deleteInstance() ,您可能不應該提供這樣的功能,因為它允許您在其他線程仍在使用T對象的情況下意外地刪除T對象。

編輯:避免重復檢查鎖定的getInstance()實現是:

static T * getInstance()
{
    lock.lock();
    if (!obj)
    {
        try
        {
            obj = new T;
        }
        catch (...)
        {
            obj = NULL;
            lock.unlock();
            throw;
        }
    }
    lock.unlock();
    return obj;
}

getInstance很好,不需要使成員volatile

但是, deleteInstance存在問題,如果從不同的線程調用它,則可能會執行雙重刪除(即使在檢查NULL )。

另一個問題是deleteInstance不會將obj重置為NULL ,因此在刪除之后, getInstance將返回一個懸空指針,而不創建新對象。

那是實現單例的一種可怕方式(而實現單例首先是個有問題的做法……)。 如果使用的編譯器支持C ++ 11的相關部分,則應使用靜態局部變量:

template<typename T>
T& getInstance() {
    static T instance;
    return instance;
}

否則,您應該使用Boost.Thread中的call_once之類的東西:

//singleton.hpp
T& getInstance();

//singleton.cpp
static T* singleton;
static boost::once_flag flag=BOOST_ONCE_INIT;

static void initialize() {
    singleton = new T();
}

T& getInstance() {
    boost::call_once(flag, initialize);
    return *singleton;
}

另一種選擇是添加一個函數來顯式初始化全局變量,然后在啟動任何新線程之前調用該函數。

您無需使變量可變。 通常,互斥鎖操作將由XCHG處理器指令實現,該指令還用作內存屏障(至少在Intel處理器上)-它將強制從內存而不是從緩存讀取變量。

如果您使用的是C ++ 11,請使obj atomic,並且getInstance所有問題都會消失。

static std::atomic<T*> obj;

template <typename T, typename Lock>
std::atomic<T*> Singleton<T, Lock>::obj = 0;

deleteInstance需要做更多的工作:

T *tmp = 0;
obj.exchange(tmp);
delete tmp;

但是deleteInstance是危險的,因為該類不知道是否存在任何未完成的指針,因此調用此函數可能會使對象從某個無辜的線程中移出。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM