2011-08-23 28 views
27

Tôi cố gắng để tạo chủ đề tính an toàn trong C# và tôi muốn chắc chắn rằng tôi trên con đường đúng - đây là những gì tôi đã làm -Chủ đề Thuộc tính an toàn trong C#

private readonly object AvgBuyPriceLocker = new object(); 
private double _AvgBuyPrice; 
private double AvgBuyPrice 
{ 
    get 
    { 
     lock (AvgBuyPriceLocker) 
     { 
      return _AvgBuyPrice; 
     } 
    } 
    set 
    { 
     lock (AvgBuyPriceLocker) 
     { 
      _AvgBuyPrice = value; 
     } 
    } 
} 

Reading gửi bài này, nó sẽ có vẻ như nếu đây không phải là cách chính xác để làm việc đó -

C# thread safety with get/set

tuy nhiên

, bài viết này dường như đề nghị ngược lại,

http://www.codeproject.com/KB/cs/Synchronized.aspx

Có ai có câu trả lời dứt khoát hơn không?

Edit:

Lý do mà tôi muốn làm các getter/setter cho thuộc tính này là b/c tôi thực sự muốn nó bắn một sự kiện khi nó được thiết lập - vì vậy mã thực sự sẽ là như thế này -

public class PLTracker 
{ 

    public PLEvents Events; 

    private readonly object AvgBuyPriceLocker = new object(); 
    private double _AvgBuyPrice; 
    private double AvgBuyPrice 
    { 
     get 
     { 
      lock (AvgBuyPriceLocker) 
      { 
       return _AvgBuyPrice; 
      } 
     } 
     set 
     { 
      lock (AvgBuyPriceLocker) 
      { 
       Events.AvgBuyPriceUpdate(value); 
       _AvgBuyPrice = value; 
      } 
     } 
    } 
} 

public class PLEvents 
{ 
    public delegate void PLUpdateHandler(double Update); 
    public event PLUpdateHandler AvgBuyPriceUpdateListener; 

    public void AvgBuyPriceUpdate(double AvgBuyPrice) 
    { 
     lock (this) 
     { 
      try 
      { 
       if (AvgBuyPriceUpdateListener!= null) 
       { 
        AvgBuyPriceUpdateListener(AvgBuyPrice); 
       } 
       else 
       { 
        throw new Exception("AvgBuyPriceUpdateListener is null"); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.Message); 
      } 
     } 
    } 
} 

Tôi khá mới làm cho chuỗi mã của mình an toàn vì vậy vui lòng cho tôi biết nếu tôi thực hiện theo cách hoàn toàn sai!

Will

+1

OK. Tôi không thấy, tại sao bạn nghĩ nó không phải. Câu trả lời SO liên kết không chỉ ra rằng đó là một ý tưởng tồi để làm điều đó như thế này. –

+0

var property = new ConcurrentValue (); property.ReadValue (x => { // sử dụng chủ đề này an toàn giá trị x }); property.WriteValue (() => { // tính toán chặn tốn thời gian phức tạp trả về "Được tính"; }); } bạn có thể tạo một lớp ConcurrentValue để sử dụng như một giá trị thuộc tính với chức năng khóa để ghi độc quyền và thực hiện nhiều lần đọc - Object.Property.Write (() => tính toán thời gian) // chặn Object.Property.Read (x =>) –

Trả lời

18

Vì bạn có một giá trị nguyên thủy khóa này sẽ làm việc tốt - vấn đề trong câu hỏi khác là giá trị tài sản là một lớp phức tạp hơn (một loại tài liệu tham khảo có thể thay đổi) - các khóa sẽ bảo vệ việc truy cập và truy lục thể hiện của giá trị kép được tổ chức bởi lớp của bạn.

Nếu giá trị thuộc tính của bạn là loại tham chiếu có thể thay đổi thì khóa khác sẽ không bảo vệ việc thay đổi thể hiện của lớp khi được truy xuất bằng phương pháp của nó, đó là những gì mà người đăng khác muốn.

6

Đọc và viết đôi là nguyên tử anyway ( source) đọc và viết của đôi không phải là nguyên tử và vì vậy nó sẽ là cần thiết để bảo vệ quyền truy cập vào một đôi sử dụng một khóa, tuy nhiên đối với nhiều loại đọc và viết là nguyên tử và vì vậy những điều sau đây sẽ an toàn:

private float AvgBuyPrice 
{ 
    get; 
    set; 
} 

Điểm của tôi là an toàn luồng phức tạp hơn là bảo vệ từng thuộc tính của bạn. Để cung cấp một ví dụ đơn giản cho rằng tôi có hai thuộc tính AvgBuyPriceStringAvgBuyPrice:

private string StringAvgBuyPrice { get; set; } 
private float AvgBuyPrice { get; set; } 

Và giả sử tôi cập nhật giá mua trung bình thusly:

this.AvgBuyPrice = value; 
this.StringAvgBuyPrice = value.ToString(); 

Đây rõ ràng không phải là thread an toàn và cá nhân bảo vệ tài sản trong cách nói trên sẽ không giúp gì cả. Trong trường hợp này, khóa phải được thực hiện ở một cấp độ khác thay vì ở mức mỗi thuộc tính.

+4

Theo nguồn MSDN của bạn, đôi * không * được đảm bảo 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 xác định, không được đảm bảo là nguyên tử. " – JeremyDWill

+0

@JeremyDWill Vâng phát hiện - Tôi đã chỉnh sửa câu hỏi của mình, tuy nhiên tôi nghĩ rằng quan điểm của tôi vẫn đứng vững. – Justin

+0

Tôi đồng ý với JeremyDWill, bạn nên xem xét rằng đôi là một số 64 bit vì vậy khi làm việc với 32 bit máy đọc và ghi các hoạt động có thể được thực hiện trong nhiều hơn một bước. –

16

Ổ khóa, như bạn đã viết chúng là vô nghĩa. Chỉ đọc biến, ví dụ: sẽ:

  1. Mua khóa.
  2. Đọc giá trị.
  3. Nhả khóa.
  4. Sử dụng giá trị đọc bằng cách nào đó.

Không có gì để ngăn chặn một chuỗi khác thay đổi giá trị sau bước 3. Khi truy cập biến trong .NET là nguyên tử (xem caveat dưới đây), khóa thực sự không đạt được nhiều ở đây: chỉ cần thêm phí. Tương phản với ví dụ đã mở khóa:

  1. Đọc giá trị.
  2. Sử dụng giá trị đọc bằng cách nào đó.

Một chủ đề khác có thể thay đổi giá trị giữa bước 1 và 2 và điều này không khác với ví dụ bị khóa.

Nếu bạn muốn đảm bảo nhà nước không thay đổi khi bạn đang làm một số chế biến, bạn phải đọc giá trị và thực hiện xử lý sử dụng mà giá trị trong CONTEX của khóa:

  1. Có được khóa .
  2. Đọc giá trị.
  3. Sử dụng giá trị đọc bằng cách nào đó.
  4. Nhả khóa.

Có trường hợp khi bạn cần khóa khi truy cập biến. Đây thường là do lý do với bộ vi xử lý cơ bản: biến số double không thể đọc hoặc viết dưới dạng lệnh đơn trên máy 32 bit, vì vậy bạn phải khóa (hoặc sử dụng chiến lược thay thế) để đảm bảo giá trị tham nhũng không đọc.

10

An toàn chủ đề không phải là thứ bạn nên thêm vào các biến của mình, đó là điều bạn nên thêm vào "logic" của mình. Nếu bạn thêm khóa vào tất cả các biến của mình, mã của bạn sẽ vẫn không nhất thiết phải là chuỗi an toàn, nhưng mã sẽ chậm như địa ngục. Để viết một chương trình an toàn theo chủ đề, Hãy xem mã của bạn và quyết định xem có thể sử dụng cùng một dữ liệu/đối tượng cho nhiều chủ đề. Thêm khóa hoặc các biện pháp an toàn khác cho tất cả những nơi quan trọng đó.

Ví dụ, giả sử các bit mã sau đây giả:

void updateAvgBuyPrice() 
{ 
    float oldPrice = AvgBuyPrice; 
    float newPrice = oldPrice + <Some other logic here> 
    //Some more new price calculation here 
    AvgBuyPrice = newPrice; 
} 

Nếu mã này được gọi là từ nhiều luồng cùng một lúc, logic khóa của bạn không có sử dụng. Hãy tưởng tượng chủ đề A nhận được AvgBuyPrice và thực hiện một số phép tính. Bây giờ trước khi nó được thực hiện, thread B cũng nhận được các tính toán AvgBuyPrice và bắt đầu. Chủ đề A trong thời gian chờ đợi được thực hiện và sẽ chỉ định giá trị mới cho AvgBuyPrice. Tuy nhiên, chỉ một lúc sau, nó sẽ bị ghi đè bởi chuỗi B (vẫn sử dụng giá trị cũ) và công việc của luồng A đã bị mất hoàn toàn.

Vậy làm cách nào để khắc phục sự cố này? Nếu chúng ta sử dụng ổ khóa (mà sẽ là giải pháp xấu nhất và chậm nhất, nhưng đơn giản nhất nếu bạn chỉ cần bắt đầu với đa luồng), chúng ta cần phải đặt tất cả các logic mà thay đổi AvgBuyPrice trong ổ khóa:

void updateAvgBuyPrice() 
{ 
    lock(AvgBuyPriceLocker) 
    { 
     float oldPrice = AvgBuyPrice; 
     float newPrice = oldPrice + <Some other code here> 
     //Some more new price calculation here 
     AvgBuyPrice = newPrice; 
    } 
} 

Bây giờ , nếu chuỗi B muốn thực hiện các phép tính trong khi luồng A vẫn bận, nó sẽ đợi cho đến khi luồng A được thực hiện và sau đó thực hiện công việc của nó bằng cách sử dụng giá trị mới. Tuy nhiên, hãy nhớ rằng bất kỳ mã nào khác cũng sửa đổi AvgBuyPrice cũng sẽ khóa AvgBuyPriceLocker trong khi mã hoạt động!

Tuy nhiên, điều này sẽ chậm nếu được sử dụng thường xuyên.Ổ khóa là tốn kém và có rất nhiều cơ chế khác để tránh khóa, chỉ cần tìm kiếm các thuật toán không khóa.

+3

Để thêm vào nhận xét của Mart, Joseph Albahari đã viết [một cuốn sách trực tuyến tuyệt vời] (http://www.albahari.com/threading/) về tất cả các khía cạnh luồng. Cuốn sách có sẵn dưới dạng HTML hoặc PDF. –

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