Vì vậy, tôi đã nhìn thấy rất nhiều bài viết bây giờ tuyên bố rằng trên C++ đôi kiểm tra khóa, thường được sử dụng để ngăn chặn nhiều chủ đề từ cố gắng để khởi tạo một singleton lười biếng tạo ra, bị hỏng. Bình thường đôi đang khóa kiểm tra lần đọc như thế này:Có vấn đề gì với việc sửa chữa khóa kép này?
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static singleton* instance;
if(!instance)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
}
return *instance;
}
};
Vấn đề rõ ràng là dòng gán dụ - trình biên dịch là miễn phí để phân bổ các đối tượng và sau đó gán con trỏ đến nó, OR để thiết lập con trỏ đến nơi nó sẽ được phân bổ, sau đó phân bổ nó. Trường hợp thứ hai phá vỡ thành ngữ - một luồng có thể cấp phát bộ nhớ và gán con trỏ nhưng không chạy hàm tạo của singleton trước khi nó được đưa vào trạng thái ngủ - sau đó luồng thứ hai sẽ thấy rằng cá thể không phải là rỗng và cố gắng trả về nó , mặc dù nó vẫn chưa được xây dựng.
I saw a suggestion để sử dụng chỉ số boolean cục bộ và kiểm tra thay vì instance
. Một cái gì đó như thế này:
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
static boost::thread_specific_ptr<int> _sync_check;
public:
static singleton & instance()
{
static singleton* instance;
if(!_sync_check.get())
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
// Any non-null value would work, we're really just using it as a
// thread specific bool.
_sync_check = reinterpret_cast<int*>(1);
}
return *instance;
}
};
Bằng cách này mỗi thread kết thúc kiểm tra nếu các trường hợp đã được tạo ra một lần, nhưng dừng lại sau đó, mà đòi hỏi phải thực hiện một số hit nhưng vẫn gần như không xấu như khóa mỗi cuộc gọi. Nhưng điều gì xảy ra nếu chúng ta vừa sử dụng một bool tĩnh cục bộ ?:
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static bool sync_check = false;
static singleton* instance;
if(!sync_check)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
sync_check = true;
}
return *instance;
}
};
Tại sao nó không hoạt động? Ngay cả khi sync_check đã được đọc bởi một sợi khi nó được gán trong một giá trị rác khác vẫn sẽ là nonzero và do đó đúng. This Dr. Dobb's article tuyên bố rằng bạn phải khóa vì bạn sẽ không bao giờ thắng một trận chiến với trình biên dịch theo hướng dẫn sắp xếp lại. Điều này khiến tôi nghĩ rằng điều này không phải vì lý do nào đó, nhưng tôi không thể hiểu tại sao. Nếu các yêu cầu về các điểm chuỗi bị mất khi bài viết của Tiến sĩ Dobb khiến tôi tin, tôi không hiểu tại sao bất kỳ mã nào sau khi khóa không thể sắp xếp lại trước khi khóa. Mà sẽ làm cho C + + đa luồng thời gian bị hỏng.
Tôi đoán tôi có thể thấy trình biên dịch được cho phép đặc biệt sắp xếp lại sync_check trước khóa bởi vì đó là biến cục bộ (và mặc dù tĩnh chúng tôi không trả về tham chiếu hoặc con trỏ tới nó) - nhưng sau đó vẫn có thể được giải quyết bằng cách biến nó thành một thành viên tĩnh (có hiệu quả toàn cầu) thay thế.
Điều này có hiệu quả hay không? Tại sao?
Vấn đề là biến có thể được gán trước khi hàm tạo đang chạy (hoặc hoàn thành), không phải trước khi đối tượng được cấp phát. – kdgregory
Cảm ơn, đã sửa. Tôi đã hoàn toàn sai lầm về điều kiện chủng tộc. –
Vâng, bạn đã đúng, hiện tại C++ thực sự là "đa luồng thời gian bị hỏng". khi chỉ xem xét tiêu chuẩn. Các nhà cung cấp trình biên dịch thường cung cấp các cách xung quanh điều này mặc dù, do đó các kết quả thực tế không phải là khủng khiếp. – Suma