2010-01-25 31 views
12

Thông thường, khi tôi muốn có một lớp học mà là thread-safe, tôi làm điều gì đó như sau:Đây có phải là thiết kế tốt để tạo các lớp học an toàn cho chủ đề trong C# không?

public class ThreadSafeClass 
{ 
    private readonly object theLock = new object(); 

    private double propertyA; 
    public double PropertyA 
    { 
     get 
     { 
      lock (theLock) 
      { 
       return propertyA; 
      } 
     } 
     set 
     { 
      lock (theLock) 
      { 
       propertyA = value; 
      } 
     } 
    } 

    private double propertyB; 
    public double PropertyB 
    { 
     get 
     { 
      lock (theLock) 
      { 
       return propertyB; 
      } 
     } 
     set 
     { 
      lock (theLock) 
      { 
       propertyB = value; 
      } 
     } 
    } 

    public void SomeMethod() 
    { 
     lock (theLock) 
     { 
      PropertyA = 2.0 * PropertyB; 
     } 
    } 
} 

Nó hoạt động, nhưng nó là rất dài dòng. Đôi khi tôi thậm chí còn tạo ra một đối tượng khóa cho mỗi phương pháp và thuộc tính tạo ra độ chi tiết và phức tạp hơn.

Tôi biết rằng cũng có thể khóa các lớp bằng cách sử dụng thuộc tính Đồng bộ hóa nhưng tôi không chắc rằng quy mô đó như thế nào - như tôi thường mong đợi có hàng trăm nghìn, nếu không phải hàng triệu trường hợp các đối tượng. Cách tiếp cận này sẽ tạo ra một bối cảnh đồng bộ hóa cho mọi cá thể của lớp, và yêu cầu lớp được bắt nguồn từ ContextBoundObject và do đó không thể bắt nguồn từ bất cứ thứ gì khác - vì C# không cho phép thừa kế nhiều - đó là một nút show trong nhiều trường hợp.

Chỉnh sửa: Khi một số người phản hồi đã nhấn mạnh, không có thiết kế lớp an toàn cho chủ đề "bạc đạn". Tôi chỉ cố gắng hiểu nếu mẫu tôi đang sử dụng là một trong những giải pháp tốt. Tất nhiên giải pháp tốt nhất trong mọi tình huống cụ thể là vấn đề phụ thuộc. Một số câu trả lời dưới đây chứa các thiết kế thay thế cần được xem xét.

Chỉnh sửa: Hơn nữa, có nhiều định nghĩa về an toàn luồng. Ví dụ: trong triển khai của tôi ở trên, mã sau sẽ KHÔNG được an toàn theo luồng:

var myObject = new ThreadSafeClass(); 
myObject.PropertyA++; // NOT thread-safe 

Vì vậy, định nghĩa lớp trên có phải là cách tiếp cận tốt không? Nếu không, bạn sẽ đề xuất gì cho một thiết kế có hành vi tương tự sẽ an toàn cho một loạt các ứng dụng tương tự?

+4

Vui lòng tìm kiếm trước: http://stackoverflow.com/search?q=What+is+the+Best+Practice+for+Creating+Thread-Safe+Classes+in+C%23 –

+0

Hm. Trong mã của bạn, bạn sử dụng cùng một đối tượng khóa để đọc cả PropertyA và PropertyB. Điều đó có nghĩa là một thread đọc PropertyA sẽ khóa một cố gắng khác để đọc PropertyB cho đến khi luồng đầu tiên hoàn tất. Đó là điều bạn muốn? Nếu đây là lớp học của tôi, tôi sẽ tạo ra một tủ khóa đọc riêng biệt cho mỗi bộ nạp và sau đó người định cư cũng phải tôn trọng khóa đó. Xem câu trả lời bên dưới. –

+0

Tôi chỉ downvoted bạn vì không có cách nào duy nhất để tạo ra một lớp thread-safe. Bất kỳ câu trả lời nào cho câu hỏi này đều gây ra ấn tượng sai lầm cho độc giả trong tương lai. –

Trả lời

8

Không có giải pháp "một kích thước phù hợp với tất cả" cho vấn đề đa luồng. Thực hiện một số nghiên cứu về tạo các lớp không thể thay đổi và tìm hiểu về các nguyên tắc đồng bộ hóa khác nhau.

Đây là ví dụ về bán bất biến hoặc các nhà lập trình không thay đổi được lớp.

public class ThreadSafeClass 
{ 
    public double A { get; private set; } 
    public double B { get; private set; } 
    public double C { get; private set; } 

    public ThreadSafeClass(double a, double b, double c) 
    { 
     A = a; 
     B = b; 
     C = c; 
    } 

    public ThreadSafeClass RecalculateA() 
    { 
     return new ThreadSafeClass(2.0 * B, B, C); 
    } 
} 

Ví dụ này sẽ chuyển mã đồng bộ của bạn vào một lớp khác và tuần tự hóa quyền truy cập vào một cá thể.Trong thực tế, bạn không thực sự muốn nhiều hơn một thread hoạt động trên một đối tượng tại bất kỳ thời điểm nào.

public class ThreadSafeClass 
{ 
    public double PropertyA { get; set; } 
    public double PropertyB { get; set; } 
    public double PropertyC { get; set; } 

    private ThreadSafeClass() 
    { 

    } 

    public void ModifyClass() 
    { 
     // do stuff 
    } 

    public class Synchronizer 
    { 
     private ThreadSafeClass instance = new ThreadSafeClass(); 
     private readonly object locker = new object(); 

     public void Execute(Action<ThreadSafeClass> action) 
     { 
      lock (locker) 
      { 
       action(instance); 
      } 
     } 

     public T Execute<T>(Func<ThreadSafeClass, T> func) 
     { 
      lock (locker) 
      { 
       return func(instance); 
      } 
     } 
    } 
} 

Đây là ví dụ nhanh về cách bạn sử dụng. Nó có vẻ hơi clunky nhưng nó cho phép bạn thực hiện nhiều hành động trên cá thể trong một lần.

var syn = new ThreadSafeClass.Synchronizer(); 

syn.Execute(inst => { 
    inst.PropertyA = 2.0; 
    inst.PropertyB = 2.0; 
    inst.PropertyC = 2.0; 
}); 

var a = syn.Execute<double>(inst => { 
    return inst.PropertyA + inst.PropertyB; 
}); 
+0

Tôi hiểu cách tiếp cận của bạn ở đây - và tôi thấy rằng trong nhiều trường hợp, nó sẽ là cách tốt nhất. Tuy nhiên, nó không cho phép thay đổi A, B và C bằng cú pháp của thuộc tính và sẽ tạo ra các vấn đề về hiệu suất nếu cập nhật thường xuyên vì mỗi lần cập nhật đòi hỏi phải tạo một đối tượng mới và cho phép GC xử lý đối tượng cũ. –

+0

Cũng không phải cho một lớp như thế này. Nhưng đối với bất kỳ loại đối tượng kinh doanh bạn là chính xác. – ChaosPandion

+1

Đây chỉ là một ví dụ về một lớp bất biến. Mặc dù về mặt kỹ thuật, chính xác là "chủ đề an toàn" (theo nghĩa là tương tác với nhiều chuỗi đồng thời sẽ không gây ra tham nhũng hoặc hành vi sai trái), đây không phải là mô hình cho "đây là cách tất cả 'các lớp' an toàn 'phải được thực hiện" . –

3

Hãy nhớ rằng thuật ngữ "an toàn chủ đề" không cụ thể; những gì bạn đang làm ở đây sẽ được gọi chính xác hơn là "đồng bộ hóa" thông qua việc sử dụng khóa Monitor.

Điều đó nói rằng, độ dài xung quanh mã được đồng bộ là khá nhiều khó tránh khỏi. Bạn có thể cắt giảm một số các khoảng trắng trong ví dụ của bạn bằng cách chuyển những thứ như thế này:

lock (theLock) 
{ 
    propertyB = value; 
} 

vào đây:

lock (theLock) propertyB = value; 

Như hay không đây là cách tiếp cận đúng cho bạn, chúng tôi thực sự cần thêm thông tin. Đồng bộ hóa chỉ là một cách tiếp cận để "an toàn luồng"; đối tượng bất biến, ẩn dụ, vv là tất cả các cơ chế khác nhau phù hợp với các trường hợp sử dụng khác nhau. Đối với ví dụ đơn giản mà bạn cung cấp (có vẻ như bạn đang cố gắng đảm bảo nguyên tử của hoạt động nhận hoặc đặt), có vẻ như bạn đã làm đúng, nhưng nếu mã của bạn được dự định là nhiều hơn minh họa hơn một ví dụ sau đó mọi thứ có thể không đơn giản như vậy.

0

Nếu bạn muốn người đọc đồng thời được phép nhưng chỉ có một người viết được phép. Lưu ý, nếu bạn có .NET 3.5, hãy sử dụng ReaderWriterLockSlim thay vì ReaderWriterLock cho loại mẫu này.

public class ThreadSafeClass 
{ 
    private readonly ReaderWriterLock theLock = new ReaderWriterLock(); 

    private double propertyA; 
    public double PropertyA 
    { 
     get 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       return propertyA; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     set 
     { 
      theLock.AcquireWriterLock(Timeout.Infinite); 
      try 
      { 
       propertyA = value; 
      } 
      finally 
      { 
       theLock.ReleaseWriterLock(); 
      } 
     } 
    } 

    private double propertyB; 
    public double PropertyB 
    { 
     get 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       return propertyB; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     set 
     { 
      theLock.AcquireWriterLock(Timeout.Infinite); 
      try 
      { 
       propertyB = value; 
      } 
      finally 
      { 
       theLock.ReleaseWriterLock(); 
      } 
     } 
    } 

    public void SomeMethod() 
    { 
     theLock.AcquireWriterLock(Timeout.Infinite); 
     try 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       PropertyA = 2.0 * PropertyB; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     finally 
     { 
      theLock.ReleaseWriterLock(); 
     } 
    } 
} 
+0

Nếu bạn có 10 thuộc tính thay vì hai thì sao ?! –

+4

Biết các công cụ của bạn, khung công tác có ReaderWriterLockSlim để thực hiện loại khóa này: http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx –

+0

Đây chính là lý do tại sao tôi hỏi nếu bạn muốn có các thuộc tính hoàn toàn đồng bộ hóa với nhau hay không. Tất cả đều dựa trên nhu cầu của bạn. Bạn giao dịch linh hoạt cho sự phức tạp. Chris 'gợi ý của ReaderWriterLockSlim (hoặc ReaderWriterLock nếu bạn muốn phiên bản HEAVYWEIGHT) sẽ làm việc để tuần tự hóa các nhà văn. –

1

Bạn có thể tìm thấy lớp học Interlocked hữu ích. Nó chứa một số hoạt động nguyên tử.

1

Một điều bạn có thể làm điều đó có thể giúp bạn tránh được mã phụ là sử dụng một cái gì đó như PostSharp để tự động tiêm những câu lệnh lock vào mã của bạn, ngay cả khi bạn có hàng trăm mã. Tất cả những gì bạn cần là một thuộc tính gắn liền với lớp và thực thi thuộc tính sẽ thêm các biến khóa bổ sung.

4

Tôi biết điều này nghe có vẻ như một câu trả lời thông minh ** nhưng ... cách tốt nhất để phát triển các lớp chủ đề là thực sự biết về đa luồng, về ý nghĩa của nó, phức tạp của nó và nó ngụ ý gì. Không có viên đạn bạc.

  • Đầu tiên you need a good reason to use it. Chủ đề là một công cụ, bạn không muốn đánh tất cả mọi thứ bằng búa mới tìm thấy của bạn.
  • Thứ hai, tìm hiểu về các vấn đề của đa luồng ... deadlocks, race conditions, starvation và vân vân
  • Thứ ba, chắc chắn là giá trị nó. Tôi đang nói về lợi ích/chi phí.
  • Cuối cùng ... hãy chuẩn bị để gỡ lỗi nặng. Debugging multithreaded code khó hơn nhiều so với mã tuần tự cũ chuẩn. Tìm hiểu một số kỹ thuật về cách thực hiện điều đó.

Nghiêm túc ... đừng cố gắng đa luồng (trong các kịch bản sản xuất có ý nghĩa) cho đến khi bạn biết mình đang làm gì ... Có thể đó là một sai lầm lớn.

Chỉnh sửa: Tất nhiên bạn nên biết nguyên tắc đồng bộ hóa của cả hệ điều hành và ngôn ngữ bạn chọn (C# trong Windows trong trường hợp này, tôi đoán).

Tôi rất tiếc vì tôi không chỉ cung cấp mã để tạo an toàn cho lớp học. Đó là bởi vì nó không tồn tại. Một lớp hoàn toàn chủ đề có thể sẽ chỉ chậm hơn là chỉ tránh các chủ đề và có thể sẽ đóng vai trò như một nút cổ chai cho bất cứ điều gì bạn đang thực hiện ... có hiệu quả hoàn tác bất cứ điều gì bạn đang đạt được bằng cách sử dụng các luồng.

+0

Tôi biết không có viên đạn bạc. Tôi có rất nhiều mã sản xuất bằng cách sử dụng mẫu tôi đã mô tả ở trên - và tôi đồng ý với bạn rằng đa luồng không nên được xem nhẹ. Tuy nhiên, ứng dụng của tôi là mạnh mẽ và không (hiện tại) bị bất kỳ deadlocks hoặc điều kiện chủng tộc - mặc dù nó có trong quá khứ. Tôi không nói mô hình này hoặc các biến thể trên nó là câu trả lời cho tất cả các vấn đề đa luồng. Tôi chỉ hỏi nếu có một cách thanh lịch hơn để có được hành vi mà mã trên có. Nó không bao giờ có vẻ lý tưởng đối với tôi và tôi đang cố gắng thu hút sự thông minh của cộng đồng. –

+5

Tôi phải nói đó là lời khuyên khá tệ. Nó giống như nói rằng một người nên trở thành một thợ thủ công bậc thầy trước mỗi khi nhặt một cái cưa. Bạn phải bắt đầu một nơi nào đó, và đây không phải là một nơi tồi tệ. –

+0

Tôi phải đồng ý với Jonathan về điều này. – ChaosPandion

3

Vì không có cách nào khác có thể làm được, dưới đây là một số phân tích về thiết kế cụ thể của bạn.

  • Bạn muốn đọc bất kỳ thuộc tính nào? Threadsafe
  • Bạn muốn cập nhật bất kỳ tài sản nào? Threadsafe
  • Bạn muốn đọc một thuộc tính duy nhất và sau đó cập nhật nó dựa trên giá trị ban đầu của nó?Không Chủ đề

Chủ đề 2 có thể cập nhật giá trị giữa đọc và cập nhật của chuỗi 1.

  • Bạn muốn cập nhật hai thuộc tính liên quan cùng một lúc? Không Chủ đề

Bạn có thể kết thúc với Thuộc tính A có giá trị của chuỗi 1 và Thuộc tính B có giá trị của chuỗi 2.

  1. Chủ đề 1 Cập nhật Một
  2. Chủ đề 2 Cập nhật Một
  3. Chủ đề 1 Cập nhật B
  4. Chủ đề 2 Update B

    • Bạn muốn đọc hai thuộc tính liên quan cùng một lúc? Không Chủ đề

Một lần nữa, bạn có thể bị gián đoạn giữa lần đọc đầu tiên và lần đọc thứ hai.

Tôi có thể tiếp tục nhưng bạn có ý tưởng. Chủ đề là hoàn toàn dựa trên cách bạn có kế hoạch để truy cập vào các đối tượng và những gì hứa hẹn bạn cần phải thực hiện.

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