2012-02-23 28 views
7

tôi chạy vào một số mã singleton ngày hôm nay trong codebase của chúng tôi và tôi đã không chắc chắn nếu những điều sau đây là thread-safe:Atomicity của C# coalescing hành

public static IContentStructure Sentence{ 
    get { 
     return _sentence ?? (_sentence = new Sentence()); 
    } 
} 

Tuyên bố này là tương đương với:

if (_sentence != null) { 
     return _sentence; 
} 
else { 
    return (_sentence = new Sentence()); 
} 

Tôi tin rằng ?? chỉ là một thủ thuật biên dịch và mã kết quả vẫn không phải là nguyên tử. Nói cách khác, hai hoặc nhiều luồng có thể tìm thấy _sentence là null trước khi thiết lập _sentence thành một câu mới và trả về nó.

Để đảm bảo tính nguyên tố, chúng tôi phải khóa mà bit mã:

public static IContentStructure Sentence{ 
    get { 

     lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); } 
    } 
} 

Có phải đó là tất cả có đúng không?

+2

http://csharpindepth.com/Articles/General/Singleton.aspx – SLaks

+2

Ngoại trừ việc bạn không thể khóa thứ gì đó không có giá trị, vì vậy giải pháp của bạn sẽ không bao giờ hoạt động. – vcsjones

+0

điểm tốt. Phải, bạn phải tạo một vật thể khác để khóa. Nắm bắt tốt. – Adam

Trả lời

11

Bạn chính xác; nó hoàn toàn không an toàn.

1

Bạn có thể sử dụng Interlocked.CompareExchange với null để nhận hoạt động bằnghoạt động trơn tru.

// I made up my own Sentence type 
Sentence current = null; 
var whenNull = new Sentence() {Text = "Hello World!"}; 

var original = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null); 

Assert.AreEqual(whenNull.Text, current.Text); 
Assert.IsNull(orig); 

// try that it won't override when not null 
current.Text += "!"; 
orig = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null); 

Assert.AreEqual("Hello World!!", current.Text); 
Assert.IsNotNull(orig); 
+0

Nhược điểm của việc đó là bạn tạo ra một 'Câu mới' cho mỗi lượt đi qua, phải không? –

+2

@DrewNoakes: Bạn là chính xác. Đó là thành ngữ hơn để làm việc trao đổi so sánh chỉ khi hiện tại là null. Sau đó, thời gian duy nhất bạn tạo đối tượng hai lần là trong cuộc đua không chắc chắn. Nếu không thể chấp nhận để tạo đối tượng hai lần thì có các kỹ thuật khác mà bạn có thể sử dụng. –

+0

Vì vậy, bạn sẽ có thể làm như sau: var x = current ?? Interlocked.CompareExchange (ref hiện tại, mới Sentence(), null) ?? hiện hành; –

15

tôi chạy vào một số mã singleton ngày hôm nay trong codebase của chúng tôi

Bạn có mã obfuscated đó trong suốt codebase của bạn? Mã này thực hiện tương tự:

if (_s == null) 
    _s = new S(); 
return _s; 

và đọc nhanh hơn hàng nghìn lần.

Tôi tin rằng ?? chỉ là một mẹo biên dịch và mã kết quả vẫn KHÔNG phải là nguyên tử

Bạn là chính xác. C# thực hiện các đảm bảo sau về nguyên tử:

Đọc và ghi các loại dữ liệu sau là nguyên tử: bool, char, byte, sbyte, ngắn, ushort, uint, int, float và tham chiếu. Ngoài ra, đọc và viết các loại enum với một kiểu cơ bản trong danh sách trước cũng là nguyên tử. Đọc và viết các loại khác, bao gồm dài, ulong, kép và thập phân, cũng như các loại do người dùng định nghĩa, không được đảm bảo là nguyên tử. Ngoài các chức năng thư viện được thiết kế cho mục đích đó, không có sự đảm bảo về đọc-sửa đổi nguyên tử, chẳng hạn như trong trường hợp tăng hoặc giảm.

Toán tử hợp nhất không nằm trong danh sách đảm bảo đó.

Để đảm bảo tính nguyên tố, chúng tôi phải khóa mà bit mã:

lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); } } }  

Tốt trời không. Tai nạn đó ngay lập tức!

Các điều đúng để làm là một trong số:

  • Dừng cố gắng để viết mã đa luồng.
  • Viết một singleton sử dụng một trong các mẫu đơn an toàn Jon Skeet tài liệu trên trang của mình về người độc thân.
  • Sử dụng lớp Lazy<T>.
  • Khóa trên một đối tượng chuyên dụng để khóa biến đó.
  • Sử dụng một trao đổi so sánh được liên kết để thực hiện kiểm tra và thiết lập nguyên tử.