單例類:
(1) 單例類保證全局只有一個唯一的實例對象。
(2) 單例類保證只有唯一的接口獲取這唯一實例。
非線程安全的單例類舉例:
1 class CSingleton 2 { 3 public: 4 ~CSingleton(){} 5 static CSingleton * getInstance() 6 { 7 if (m_instance == nullptr) 8 { 9 m_instance = new CSingleton; 10 } 11 return m_instance; 12 } 13 static void delInstance() 14 { 15 if (m_instance) 16 { 17 delete m_instance; 18 m_instance = nullptr; 19 } 20 } 21 void print() 22 { 23 std::cout << "print test" << std::endl; 24 } 25 private: 26 CSingleton(){} 27 CSingleton & operator=(const CSingleton & ) = delete; 28 CSingleton(const CSingleton &) = delete; 29 private: 30 static CSingleton * m_instance; 31 }; 32 33 CSingleton * CSingleton::m_instance = nullptr;
上述單例類面對多線程並發訪問時會出錯。
看如下線程安全的單例類(非C++11實現)
1 class CSingleton 2 { 3 public: 4 ~CSingleton() {} 5 static CSingleton * getInstance() 6 { 7 if (m_instance == nullptr) 8 { 9 std::lock_guard<std::mutex> lgd(m_mt); 10 if (m_instance == nullptr) 11 { 12 m_instance = new CSingleton; 13 } 14 } 15 return m_instance; 16 } 17 static void delInstance() 18 { 19 std::lock_guard<std::mutex> lgd(m_mt); 20 if (m_instance) 21 { 22 delete m_instance; 23 m_instance = nullptr; 24 } 25 } 26 void print() 27 { 28 std::cout << "print test" << std::endl; 29 } 30 private: 31 CSingleton() {} 32 CSingleton & operator=(const CSingleton & ) = delete; 33 CSingleton(const CSingleton &) = delete; 34 private: 35 static CSingleton * m_instance; 36 static std::mutex m_mt; 37 }; 38
39 CSingleton * CSingleton::m_instance = nullptr; 40 std::mutex CSingleton::m_mt;
當然絕對的線程安全還是有問題,因為C++創建對象時,會執行1、分配內存,2 調用構造,3 賦值操作三步操作,然而現代CPU和編譯器高並發下可能
會進行亂序重排操作,因而創建對象new CSingleton的第2步可能會晚於第3步進行指令調用,因而導致出現未定義的的行為。
舉例:
線程A : getInstance 判斷 instance是否為空,為空則
線程A : 分配內存 此時CPU亂序指令重排,賦值操作提前
線程B : getInsnace 判斷instance是否為空,非空,則返回
線程B : 使用了未初始化的instacne 出現未定義行為。
線程A : 調用構造函數對instance初始化。
因此要解決上述問題需要引入內存柵欄來確保指令運行的同步性。在CPU指令重排的前提下保持數據的一致性。
C++11支持線程安全的單例類:
C++11的單例模式的實現
1 class CSingleton 2 { 3 public: 4 ~CSingleton() {} 5 static CSingleton & getInstance() 6 { 7 static CSingleton m_instance; 8 return m_instance; 9 } 10 void print() 11 { 12 std::cout << "print test" << std::endl; 13 } 14 };
返回靜態局部對象的引用,C++11中是線程安全的。
驗證一下:
1 class CStatic 2 { 3 public: 4 CStatic() 5 { 6 std::cout << "construct begin" << std::endl; 7 Sleep(5000); 8 std::cout << "construct end" << std::endl; 9 } 10 void print() 11 { 12 std::cout << "print" << std::endl; 13 std::cout << s_num++ << std::endl; 14 } 15 static int s_num; 16 static std::mutex s_mt; 17 };
1 int CStatic::s_num = 0; 2 std::mutex CStatic::s_mt; 3
4 // 5 void thread_func() 6 { 7 static CStatic st; 8 st.print(); 9 } 10
11 int main() 12 { 13 std::vector<std::thread> vecThread; 14 for (auto i = 0; i< 8; i++) 15 { 16 vecThread.push_back(std::thread(thread_func)); 17 } 18 for (auto i = 0; i< 8; i++) 19 { 20 vecThread[i].join(); 21 } 22 // 23 system("pause"); 24 return 0; 25 }
首先我們創建一個CStatic類,然后創建8個線程來啟動thread_func(),thread_func()初始化了一個靜態CStatic對象,(靜態局部變量僅被初始化一次)
然后接着運行。我們發現,當首個線程初始化CStatic時,其他線程都是被阻塞的,從構造函數的begin和end中可以看到,我們故意讓其停留5s,
如下圖,其他線程都是在st被初始化之后才運行。

所以CStatic靜態局部對象被構造的過程中是線程安全的,但是其擁有的成員變量則不是線程安全的。
因此我們增加個簡單的鎖,
1 class CStatic 2 { 3 public: 4 CStatic() 5 { 6 std::cout << "construct begin" << std::endl; 7 Sleep(5000); 8 std::cout << "construct end" << std::endl; 9 } 10 void print() 11 { 12 std::lock_guard<std::mutex> lgd(s_mt); 13 std::cout << "print" << std::endl; 14 std::cout << s_num++ << std::endl; 15 } 16 static int s_num; 17 static std::mutex s_mt; 18 };

