2009-11-02 21 views
105

Thực hiện sau đây, sử dụng khởi tạo lười biếng, luồng an toàn Singleton (Meyers 'Singleton) có an toàn không?Việc triển khai chuỗi mô hình Singleton có an toàn không?

static Singleton& instance() 
{ 
    static Singleton s; 
    return s; 
} 

Nếu không, tại sao và làm thế nào để làm cho chuỗi an toàn?

+0

Ai đó có thể giải thích tại sao đây không phải là chủ đề an toàn. Các bài viết được đề cập trong các liên kết thảo luận về an toàn luồng bằng cách sử dụng một triển khai thay thế (sử dụng biến con trỏ tức là tĩnh Singleton * pInstance). – Ankur

+1

Xem: http: // stackoverflow.com/questions/449436/singleton-instance-declaration-as-static-biến-of-getinstance-method/449823 # 449823 –

+0

Xem: http://stackoverflow.com/questions/1008019/c-singleton-design-pattern/ 1008289 # 1008289 –

Trả lời

124

Trong C++11, đó là chỉ an toàn. Theo standard, §6.7 [stmt.dcl] p4:

Nếu kiểm soát vào tờ khai đồng thời trong khi các biến được khởi tạo, thực hiện đồng thời phải chờ phải hoàn thành việc khởi tạo.

GCC và hỗ trợ VS cho tính năng (Dynamic Initialization and Destruction with Concurrency, còn được gọi là Magic Statics on MSDN) như sau:

Nhờ @Mankarse và @olen_gam vì nhận xét của họ.


Trong C++03, mã này không an toàn. Có một bài báo của Meyers được gọi là "C++ and the Perils of Double-Checked Locking", trong đó thảo luận về việc triển khai an toàn luồng của mô hình, và kết luận là, nhiều hơn hoặc ít hơn, đó là khóa đơn giản nhất để đảm bảo sự đồng thời thích hợp trên tất cả các nền tảng, trong khi hầu hết các dạng của các biến thể mẫu khóa được kiểm tra kép có thể bị race conditions on certain architectures, trừ khi các lệnh được xen kẽ với các rào cản bộ nhớ có vị trí chiến lược.

+4

+1 cho liên kết đến các bài viết hay. – Ankur

+2

Ngoài ra còn có một cuộc thảo luận rộng rãi về Mẫu Singleton (đời và chủ đề an toàn) của Alexandrescu trong Thiết kế C++ Hiện đại. Xem trang web của Loki: http://loki-lib.sourceforge.net/index.php?n=Pattern.Singleton –

+0

Bạn có thể tạo một singleton chủ đề an toàn với tăng :: call_once. – CashCow

6

Câu trả lời đúng tùy thuộc vào trình biên dịch của bạn. Nó có thể quyết định làm cho chủ đề an toàn; nó không phải là "an toàn" chủ đề an toàn.

18

Để trả lời câu hỏi của bạn về lý do tại sao nó không an toàn, không phải vì cuộc gọi đầu tiên đến instance() phải gọi hàm tạo cho Singleton s. Để trở thành chủ đề, điều này sẽ phải xảy ra trong một phần quan trọng, và không có yêu cầu trong tiêu chuẩn rằng một phần quan trọng được thực hiện (tiêu chuẩn cho đến nay hoàn toàn im lặng về các chủ đề). Các trình biên dịch thường thực hiện điều này bằng cách sử dụng một phép kiểm tra đơn giản và gia tăng của một boolean tĩnh - nhưng không phải trong một phần quan trọng. Nội dung như mã giả sau đây:

static Singleton& instance() 
{ 
    static bool initialized = false; 
    static char s[sizeof(Singleton)]; 

    if (!initialized) { 
     initialized = true; 

     new(&s) Singleton(); // call placement new on s to construct it 
    } 

    return (*(reinterpret_cast<Singleton*>(&s))); 
} 

Vì vậy, đây là một Singleton đơn giản an toàn chỉ dành cho Windows. Nó sử dụng một trình bao bọc lớp đơn giản cho đối tượng Windows CRITICAL_SECTION để chúng ta có thể có trình biên dịch tự động khởi tạo CRITICAL_SECTION trước khi gọi là main(). Lý tưởng một lớp phần quan trọng RAII thực sự sẽ được sử dụng mà có thể đối phó với các ngoại lệ có thể xảy ra khi phần quan trọng được tổ chức, nhưng đó là vượt ra ngoài phạm vi của câu trả lời này.

Hoạt động cơ bản là khi một phiên bản yêu cầu Singleton được yêu cầu, khóa được thực hiện, Singleton được tạo nếu cần, sau đó khóa được giải phóng và tham chiếu Singleton được trả về.

#include <windows.h> 

class CritSection : public CRITICAL_SECTION 
{ 
public: 
    CritSection() { 
     InitializeCriticalSection(this); 
    } 

    ~CritSection() { 
     DeleteCriticalSection(this); 
    } 

private: 
    // disable copy and assignment of CritSection 
    CritSection(CritSection const&); 
    CritSection& operator=(CritSection const&); 
}; 


class Singleton 
{ 
public: 
    static Singleton& instance(); 

private: 
    // don't allow public construct/destruct 
    Singleton(); 
    ~Singleton(); 
    // disable copy & assignment 
    Singleton(Singleton const&); 
    Singleton& operator=(Singleton const&); 

    static CritSection instance_lock; 
}; 

CritSection Singleton::instance_lock; // definition for Singleton's lock 
             // it's initialized before main() is called 


Singleton::Singleton() 
{ 
} 


Singleton& Singleton::instance() 
{ 
    // check to see if we need to create the Singleton 
    EnterCriticalSection(&instance_lock); 
    static Singleton s; 
    LeaveCriticalSection(&instance_lock); 

    return s; 
} 

Man - đó là rất nhiều thứ để "tạo nên một toàn cầu tốt hơn".

Những nhược điểm chính để implemention này (nếu tôi đã không để một số lỗi lọt qua) là:

  • nếu new Singleton() ném, khóa sẽ không được phát hành. Điều này có thể được cố định bằng cách sử dụng một đối tượng khóa RAII thực sự thay vì đối tượng khóa đơn giản mà tôi có ở đây. Điều này cũng có thể giúp làm cho mọi thứ di động nếu bạn sử dụng một cái gì đó như Boost để cung cấp một nền tảng độc lập wrapper cho khóa.
  • điều này đảm bảo an toàn luồng khi yêu cầu Singleton sau khi gọi main() - nếu bạn gọi nó trước đó (như khởi tạo đối tượng tĩnh) mọi thứ có thể không hoạt động vì CRITICAL_SECTION có thể không được khởi tạo.
  • một khóa phải được thực hiện mỗi khi một yêu cầu được yêu cầu. Như tôi đã nói, đây là một chuỗi thực hiện an toàn đơn giản. Nếu bạn cần một cái tốt hơn (hoặc muốn biết tại sao những thứ như kỹ thuật khóa kiểm tra kép là thiếu sót), hãy xem papers linked to in Groo's answer.
+0

Giải thích tốt. Cảm ơn. – Ankur

+8

+1 'đó là rất nhiều crap để "làm cho một toàn cầu tốt hơn" - đó có một tiếng cười! ;-) –

+1

Uh oh. Điều gì sẽ xảy ra nếu 'Singleton mới()' ném? – sbi

5

Chủ đề [...] sau đây có an toàn không?

Trên hầu hết các nền tảng, đây không phải là an toàn chỉ. (Gắn tuyên bố từ chối trách nhiệm thông thường giải thích rằng tiêu chuẩn C++ không biết về chủ đề, vì vậy, về mặt pháp lý, nó không nói là có hay không.)

Nếu không, tại sao [...]?

Lý do không có nghĩa là không có gì ngăn cản nhiều hơn một chuỗi đồng thời thực hiện tác vụ xây dựng s '.

cách làm cho chuỗi này an toàn?

"C++ and the Perils of Double-Checked Locking" bởi Scott Meyers và Andrei Alexandrescu là một luận án khá tốt về chủ đề của người độc thân an toàn.

2

Như MSalters đã nói: Nó phụ thuộc vào việc thực hiện C++ mà bạn sử dụng. Kiểm tra tài liệu. Đối với câu hỏi khác: "Nếu không, tại sao?" - Tiêu chuẩn C++ chưa đề cập gì về chủ đề. Nhưng phiên bản C++ sắp tới là nhận biết các luồng và nó nói rõ rằng việc khởi tạo các địa phương tĩnh là an toàn luồng. Nếu hai chủ đề gọi một hàm như vậy, một luồng sẽ thực hiện khởi tạo trong khi luồng khác sẽ chặn & đợi cho nó kết thúc.

9

Nhìn vào tiêu chuẩn tiếp theo (phần 6.7.4), nó khám phá cách khởi tạo địa phương tĩnh là luồng an toàn. Vì vậy, một khi phần tiêu chuẩn được triển khai rộng rãi, Singleton của Meyer sẽ được thực hiện ưu tiên.

Tôi không đồng ý với nhiều câu trả lời rồi. Hầu hết các trình biên dịch đã thực hiện khởi tạo tĩnh theo cách này. Một ngoại lệ đáng chú ý là Microsoft Visual Studio.

Các vấn đề liên quan