2011-09-13 38 views
5

Tôi có cần phải thêm một khối khóa ở đây nếu tôi muốn chắc chắn rằng cá thể đó sẽ được tạo chỉ 1 lần?Tôi có cần khóa ở đây không?

 if (instance==null) 
     { 
      instance = new Class(); 
     } 

Vì chỉ có 1 lệnh trong IF tôi không chắc chắn 100%. Trong một trường hợp như dưới đây, tôi chắc chắn tôi sẽ cần nó nhưng tôi muốn kiểm tra lại nếu cùng áp dụng cho các mã trên.

 if (instance==null) 
     { 
      int i = 5; 
      int y = 78; 
      instance = new Class(i, y); 
     } 

EDIT

Vâng, tôi giả đa luồng

+2

Có lẽ giá trị xem xét [Thực hiện Singleton Mẫu trong C#] (http://csharpindepth.com/Articles/General/Singleton.aspx) –

Trả lời

7

Có, bạn cần khóa trong cả hai ví dụ của mình. Hãy số các dòng để làm cho lời giải thích dễ dàng hơn:

1 if (instance == null) 
2 { 
3  instance = new Class(); 
4 } 

Bây giờ, giả sử bạn có hai luồng, A và B. Cả hai chủ đề đang thực hiện mã này. Đầu tiên, A kiểm tra instance tại dòng 1, và vì nó không có đường dẫn đúng - trên dòng 3. Sau đó, bạn nhận được một chuyển đổi ngữ cảnh trước khi dòng 3 được thực hiện và B thực hiện tương tự (đúng trên dòng 1 và kết thúc tại dòng 3). Bây giờ, cả hai chuỗi đều nằm trong phần nội dung câu hỏi if của bạn và bạn sẽ nhận được hai bài tập cho instance.

9

Nếu bạn đang làm việc đa luồng thì câu trả lời là CÓ.

lưu ý nhỏ:

Rõ ràng bạn không thể đặt một khóa sử dụng ví dụ, bởi vì nó là null :-)

Các Khóa mô hình là bình thường (nó được gọi là Double-checked locking):

if (instance == null) 
{ 
    lock (something) 
    { 
     if (instance == null) 
     { 
      instance = new Class(); 
     } 
    } 
} 

Nếu bạn muốn (và nếu tạo Lớp không đắt) bạn có thể làm:

if (instance == null) 
{ 
    Interlocked.CompareExchange(ref instance, new Class(), null); 
    // From here you are sure the instance field containts a "Class" 
} 

Vấn đề duy nhất với đoạn mã này là hai luồng có thể tạo một lớp mới(), nhưng chỉ có một đối tượng mới có thể thiết lập cá thể với tham chiếu lớp mới(), vì vậy một đối tượng khác sẽ tạo ra một đối tượng Class vô dụng đó sẽ là GC. Nếu tạo đối tượng Class là không tốn kém (ví dụ như tạo ra một List<T>) nó là ok. Nếu việc tạo các đối tượng Class là tốn kém (có lẽ bởi vì constructor gọi một DB làm một truy vấn lớn lớn) thì phương thức này là một không-không.

Ghi chú nhỏ: Grinch đã đến và nó đã tuyên bố rằng tất cả luồng đa luồng này không đủ "chống lừa đảo". Và anh ấy đúng. Và anh ta sai. Nó giống như mèo của Schrödinger! :-) Lần cuối cùng tôi viết một chương trình đa luồng, tôi mất một tuần chỉ đọc tất cả các tài liệu có trên internet. Và tôi vẫn còn lỗi (nhưng ok, tôi đã cố gắng để viết mã MT không khóa ... Nó khá nặng mumbo jumbo). Vì vậy, sử dụng Lazy<T> nếu bạn đang sử dụng .NET 4.0, và nếu không tốt ... Hãy lấy nguồn đơn và tìm nơi Lazy được định nghĩa và sao chép nó :-) (giấy phép khá chấp nhận, nhưng đọc nó!) NHƯNG Nếu cái gì bạn cần là một singleton, bạn có thể sử dụng static Lazy<T> HOẶC nếu bạn không có .NET 4.0, bạn có thể sử dụng một trong các mẫu từ http://www.yoda.arachsys.com/csharp/singleton.html (thứ năm hoặc thứ tư. Tác giả đề xuất câu hỏi thứ tư. là một số lời khuyên thú vị về sự lười biếng của nó, nhưng chúng được viết bằng http://www.yoda.arachsys.com/csharp/beforefieldinit.html). Hãy lưu ý rằng bạn phải viết chúng "như được viết". Đừng bao giờ nghĩ đến việc sai lệch so với những gì nó được viết. KHÔNG. Như bạn có thể thấy đọc bằng các bình luận, luồng là một đối số HARD. Bạn có thể đúng và bạn vẫn có thể sai cùng một lúc. Nó giống như dễ bay hơi (dễ bay hơi? Nó là một trò chơi chữ?) Các hợp chất hóa học ... Rất rất buồn cười và rất nguy hiểm.

Một lưu ý nhỏ khác: dưới .NET 1.1 nó thực sự rất dính. Bạn không nên chia sẻ các biến giữa các chủ đề trong 1.1 trừ khi bạn biết chính xác những gì bạn đang làm. Trong 2.0 họ đã thay đổi mô hình bộ nhớ (cách trình biên dịch có thể tối ưu hóa truy cập vào bộ nhớ) và chúng tạo ra một mô hình bộ nhớ “an toàn hơn”. .NET 1.1 và các phiên bản Java cũ hơn (lên đến 1.4 bao gồm) là một cái bẫy pit.


Để trả lời câu hỏi của bạn, một mẹo đơn giản: khi bạn nghĩ "có thể phá vỡ mã của tôi?" làm điều này: hãy tưởng tượng rằng Windows (hệ điều hành) là một kẻ lười biếng lười biếng. Đôi khi anh ta dừng một thread trong nửa giờ trong khi để cho các luồng khác chạy (về mặt kỹ thuật, anh ta có thể làm điều đó. Không phải trong 30 phút (nhưng không có quy tắc thực sự dài bao nhiêu), nhưng trong mili giây nó có thể thực hiện được. là một chút quá tải với công việc và có nhiều chủ đề được gắn vào bộ vi xử lý cụ thể (có nghĩa là họ đã nói với hệ điều hành rằng họ muốn chạy CHỈ trên một số bộ vi xử lý cụ thể. 1 thread không được ghim, nó khá rõ ràng rằng các thread trên bộ vi xử lý 2 sẽ đi nhanh hơn nhiều!)). Vì vậy, hãy tưởng tượng rằng hai luồng nhập đoạn mã của bạn cùng một lúc, thực thi các dòng đầu tiên cùng một lúc (chúng nằm trên hai bộ xử lý khác nhau, chúng có thể được thực thi song song), nhưng một trong hai luồng được dừng lại và có chờ 30 phút. Điều gì sẽ xảy ra trong thời gian đó? Và lưu ý rằng mã thường có thể dừng lại ở giữa lệnh! a = a + 1 là hai hướng dẫn! Đó là var temp = a + 1; a = temp; Nếu bạn áp dụng thủ thuật này cho mã ví dụ của bạn, nó khá dễ thấy: cả hai chủ đề thực hiện if (instance==null) và vượt qua, một luồng được dừng lại trong 30 phút, một luồng khác khởi tạo đối tượng, cái đầu tiên được tiếp tục và khởi tạo vật. Hai khởi tạo đối tượng. Không :-) tốt

Tôi sẽ giải thích vấn đề .NET 1.1 với một ví dụ đơn giản:

class MyClass 
{ 
    public bool Initialized; 

    public MyClass() 
    { 
     Initialized = true; 
    } 
} 

MyClass instance = null; 

public MyClass GetInstance() 
{ 
    if (instance == null) 
    { 
     lock (something) 
     { 
      if (instance == null) 
      { 
       instance = new Class(); 
      } 
     } 
    } 

    return instance; 
} 

Bây giờ ... Đây là mã từ trước. Sự cố xảy ra ở dòng instance = new Class(). Hãy chia nhỏ nó ở các phần khác nhau:

  1. Không gian cho đối tượng Lớp được phân bổ bởi .NET. Tham chiếu đến không gian này được lưu ở đâu đó.
  2. Các nhà xây dựng của Lớp được gọi. Initialized = true (trường được gọi là Initialized!).
  3. Biến mẫu được đặt thành tham chiếu mà chúng tôi đã lưu trước đó.

Trong mô hình bộ nhớ "mạnh" mới hơn của .NET 2.0, các thao tác này sẽ xảy ra theo thứ tự này. Nhưng chúng ta hãy xem những gì có thể xảy ra uner .NET 1.1: viết có thể được sắp xếp lại !!

  1. Không gian cho đối tượng Lớp được phân bổ bởi .NET. Tham chiếu đến không gian này được lưu ở đâu đó.
  2. Biến mẫu được đặt thành tham chiếu mà chúng tôi đã lưu trước đó.
  3. Các nhà xây dựng của Lớp được gọi. Initialized = true (trường được gọi là Initialized!).

Bây giờ, hãy tưởng tượng rằng chuỗi làm việc này bị hệ điều hành tạm ngưng trong 30 phút sau điểm 2 và trước điểm 3. Một chuỗi khác có thể truy cập biến mẫu (lưu ý rằng đầu tiên nếu mã không được bảo vệ bởi một khóa, vì vậy nó có thể truy cập nó mà không cần chờ đợi thread đầu tiên kết thúc công việc của nó) và sử dụng nó. Nhưng Class không thực sự được khởi tạo: constructor của anh ta đã không chạy! Boooom! Nếu bạn đã sử dụng từ khóa volatile trên khai báo cá thể (vì vậy volatile MyClass instance = null;) điều này sẽ không xảy ra, bởi vì trình biên dịch không thể sắp xếp lại quá khứ viết trên một trường dễ bay hơi. Vì vậy, anh ta không thể sắp xếp lại điểm 2 sau điểm 3 vì ở điểm 3 nó đang viết trên một trường dễ bay hơi. Nhưng như tôi đã viết, đây là một vấn đề của .NET 1.1.

Hiện tại.nếu bạn muốn tìm hiểu về luồng, vui lòng đọc: http://www.albahari.com/threading/ Nếu bạn muốn biết điều gì xảy ra "đằng sau hậu trường", bạn có thể đọc http://msdn.microsoft.com/en-us/magazine/cc163715.aspx nhưng nó khá nặng.

+4

Ngay cả câu trả lời là CÓ. – Thomas

+0

khóa kiểm tra kép bị hỏng –

+0

Tốt hơn nên sử dụng 'Lazy '- loại này an toàn và đơn giản hơn nhiều để có được quyền. –

2

Nếu bạn đang sử dụng nhiều chủ đề, vâng. Nhiều luồng có thể nhập câu lệnh if trước khi bất kỳ câu lệnh nào trong số chúng thực sự thiết lập cá thể cho lớp mới.

8

Nếu đây là đa luồng, thì có, bạn cần khóa hoặc một số hình thức đồng bộ hóa.

Tuy nhiên, nếu điều này là để cho phép thuyết minh lười biếng, tôi khuyên bạn nên sử dụng Lazy<T>. Nó xử lý an toàn chủ đề cho bạn, và loại bỏ sự cần thiết cho việc kiểm tra.

+0

Xin lỗi, tôi đang sử dụng .NET 3.5 – StackOverflower

+0

@Timmy Nếu tôi nhớ chính xác, Lazy được giới thiệu trong Khung phản ứng với phần lớn các bộ sưu tập đồng thời mà chúng đã giới thiệu trong C# 4.0 – xanatos

+0

@Timmy: Nó là một phần của khuôn khổ đó. Đó là một cách tuyệt vời để có được nó và TPL cho .NET 3.5. –

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