2010-08-19 30 views
8

thể trùng lặp:
Are C# auto-implemented static properties thread-safe?Nhận và thiết lập một chuỗi thuộc tính tĩnh đơn giản có an toàn không?

Trong ví dụ lớp sau

static class Shared 
{ 
    public static string[] Values { get; set; } 
} 

nhiều bài đọc đọc mảng Values chuỗi theo định kỳ, trong khi bất cứ lúc nào một nhà văn độc thân sẽ thay thế toàn bộ mảng bằng một giá trị mới bằng cách sử dụng setter. Tôi có cần phải sử dụng một số ReaderWriterLock hoặc điều này C# sẽ xử lý tự động không?

Chỉnh sửa: Trong trường hợp của tôi, yêu cầu "an toàn chỉ" là: Không có gì xấu xảy ra khi người viết thay thế mảng trong khi người đọc đang tìm kiếm giá trị. Tôi không quan tâm liệu người đọc sẽ sử dụng giá trị mới ngay lập tức miễn là họ sẽ sử dụng giá trị đó trong tương lai

+2

Bản sao của http://stackoverflow.com/questions/2074670/are-c-auto-implemented-static-properties-thread-safe? –

+0

Kiểm tra không gian tên 'System.Collections.Concurrent' nếu bạn đang ở trong .NET 4. Tùy thuộc vào cách sử dụng của bạn, chúng có thể phù hợp với nhu cầu của bạn. – Marc

Trả lời

10

sử dụng Đây là chủ đề an toàn:

string[] localValues = Shared.Values; 
for (int index = 0; index < localValues.length; index++) 
    ProcessValues(localValues[index]); 

sử dụng này không phải là thread an toàn, và có thể dẫn đến trường hợp ngoại lệ out-of-bounds:

for (int index = 0; index < Shared.Values.Length; index++) 
    ProcessValues(Shared.Values[index]); 

Tôi muốn làm chuỗi cuộc gọi an toàn chủ đề tự nhiên hơn bằng cách thực hiện một việc như sau:

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() { return values; } 
    public static void SetValues(string[] values) { Shared.values = values; } 
} 

Tất nhiên, người dùng vẫn có thể đặt GetValues ​​() trong các vòng lặp, và nó sẽ chỉ là xấu, nhưng ít nhất nó rõ ràng là xấu.

Tùy thuộc vào tình huống, giải pháp thậm chí còn tốt hơn có thể là phát các bản sao, để mã gọi điện không thể đột biến mảng đó. Đây là những gì tôi thường làm, nhưng nó có thể không thích hợp trong hoàn cảnh của bạn.

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() 
    { 
     string[] currentValues = values; 
     if (currentValues != null) 
      return (string[])currentValues.Clone(); 
     else 
      return null; 
    } 
    public static void SetValues(string[] values) 
    { 
     Shared.values = values; 
    } 
} 
+2

Để thêm vào câu trả lời này: không cần khóa trong kịch bản đọc lười này vì việc tham chiếu đến mảng được chia sẻ đóng vai trò là một hoạt động nguyên tử.Việc đọc một tham chiếu (con trỏ) từ một biến chia sẻ là luồng an toàn ngay cả khi một luồng khác sắp viết một giá trị mới cho biến chia sẻ. Đây là một dạng phiên bản "thế hệ" - mỗi lần bạn tham chiếu đến dữ liệu được chia sẻ, về cơ bản nó là một ảnh chụp nhanh bất biến của dữ liệu, được giữ cho đến khi tất cả các tham chiếu đến nó được phát hành. – dthorpe

+0

Làm rõ: Số đọc của các con trỏ tham chiếu là nguyên tử ở cấp phần cứng trên x86 và x64 khi địa chỉ bộ nhớ được liên kết dword, mà tôi chắc chắn được đảm bảo bởi trình cấp phát .NET. Các lần đọc không được ký hiệu có thể mất nhiều chu kỳ đồng hồ để đọc mỗi nửa và kết hợp chúng, và điều đó tạo ra một cửa sổ cơ hội để ghi vào một chuỗi khác để thay đổi giá trị trong bộ nhớ trước khi đọc đã đọc phần thứ hai. – dthorpe

+3

@dthorpe - Điều quan trọng là định nghĩa ngôn ngữ C# đảm bảo rằng một tham chiếu đến một đối tượng sẽ được sao chép trong một fasion nguyên tử bất kể nền tảng mà nó đang chạy hoặc cơ chế được triển khai như thế nào. Và đó là một điều khá hữu ích cho những hoàn cảnh như thế này. :-) –

2

Đây không phải là chủ đề an toàn. Có, bạn sẽ cần phải sử dụng một khóa.

+1

Câu trả lời này sẽ hữu ích hơn nhiều (hoặc có thể sai) nếu nó chứa một số chi tiết - như những nguy hiểm mà bạn thấy trước. –

+0

"Thread-safe" và "not thread-safe" được sử dụng quá lỏng lẻo. Trong trường hợp này, đọc và viết được đảm bảo là nguyên tử, nhưng có thể không theo thứ tự. Một khóa giữ trật tự, nhưng đặt hàng có thể không quan trọng ở đây. –

5

Điều đó phụ thuộc vào ý bạn là an toàn chỉ.

Đọc và thay thế được đảm bảo là nguyên tử, nhưng không đảm bảo rằng đọc sau ghi sẽ nhất thiết phải đọc giá trị mới.

Để đối phó với bản chỉnh sửa của ...

Không có gì xấu sẽ xảy ra với mã hiện tại của bạn (ví dụ, bị rách lần đọc), nhưng không có gì bảo đảm rằng độc giả của bạn sẽ bao giờ thấy giá trị mới . Ví dụ, nó có thể - mặc dù có lẽ không - rằng tài liệu tham khảo cũ sẽ được lưu trữ trong một đăng ký mãi mãi.

+0

Tôi đã cập nhật câu hỏi của mình với mức độ an toàn của chuỗi yêu cầu. – xsl

7

Không cần bảo vệ thêm, không có bảo đảm rằng chuỗi đọc sẽ bao giờ xem giá trị mới. Trong thực tế, miễn là chuỗi đọc đang làm bất kỳ điều gì quan trọng, nó sẽ xem giá trị mới. Đặc biệt, bạn sẽ không bao giờ thấy "một nửa" bản cập nhật, với tham chiếu trỏ đến không gian chết.

Nếu bạn thực hiện trường volatile, tôi tin rằng ngay cả nguy cơ này cũng bị xóa - nhưng lập trình không có khóa thường khó có lý do. Điều này có nghĩa là từ bỏ nó là một tài sản được tự động thực hiện, tất nhiên.

+0

@ LukeH: Có thể ... sự hiểu biết của tôi cũng bị hạn chế. Tuy nhiên, tôi tin rằng nó "nhiều hơn hoặc ít hơn" đảm bảo bạn luôn đọc giá trị cuối cùng. (Đó là ngay cả những mô tả MSDN của nó.) –

1

Tôi sẽ khóa. Đối với nhiều lần đọc, thỉnh thoảng viết sử dụng ReaderWriterLockSlim - hiệu quả hơn nhiều so với ReaderWriteLock.

static class Shared 
{ 
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 
    private static string[] _values; 

    public static string[] Values 
    { 
     get 
     { 
      _rwLock.EnterReadLock(); 
      try 
      { 
       return _values; 
      } 
      finally 
      { 
       _rwLock.ExitReadLock(); 
      } 
     } 
     set 
     { 
      _rwLock.EnterWriteLock(); 
      try 
      { 
       _values = value; 
      } 
      finally 
      { 
       _rwLock.ExitWriteLock(); 
      } 
     } 
    } 
} 
+0

Một đồng bằng cũ 'khóa' sẽ được nhanh hơn vẫn còn. Lý do là ổ khóa RW có một tập hợp giới hạn các kịch bản mà chúng thực sự nhanh hơn trong thực tế. Đây không phải là một trong số họ. Tôi chỉ làm một điểm chuẩn trên máy tính của tôi và một 'khóa' là khoảng 5x nhanh hơn. –

+0

Tôi đồng ý với Brian, một khóa nhanh hơn, nhưng nếu chúng ta đang nói về một số lượng lớn các luồng truy cập dữ liệu cùng một lúc, thì có lẽ điều này có thể có một số lợi thế. Về mặt con người, sự khác biệt là khá không đáng kể! – Michael

1

Để đi hơi lạc đề, Java 2 mô hình 1.5 bộ nhớ bổ sung thêm hai khoản bảo lãnh (xem Java theory and practice: Fixing the Java Memory Model, Part 2):

  • biến động đọc/viết cũng là rào cản bộ nhớ.
  • final trường xuất hiện hoàn toàn được khởi tạo bên ngoài hàm tạo.

Sau đó là một trường hợp thú vị: bạn sử dụng để có thể làm foo = new String(new char[]{'a','b','c'}); trong một thread và foo = "123"; System.out.println(foo) trong chủ đề khác và in chuỗi rỗng, vì tôi chẳng có gì đảm bảo rằng viết cho lĩnh vực thức foo sẽ xảy ra trước viết thư cho foo.

Tôi không chắc chắn chi tiết về việc khởi tạo mảng .NET; nó có thể là trường hợp mà bạn cần phải sử dụng Thread.MemoryBarrier() (ở đầu của getter và ở cuối của setter) để đảm bảo rằng người đọc chỉ nhìn thấy các mảng được khởi tạo hoàn toàn.

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