2011-11-25 32 views
42

Tôi đã cố gắng để lock một biến Boolean khi tôi gặp phải những lỗi sau:Tại sao chúng ta không thể khóa trên một loại giá trị?

'bool' không phải là một loại tài liệu tham khảo theo yêu cầu của báo cáo kết quả khóa

Dường như chỉ các loại tài liệu tham khảo là được phép trong các câu hỏi lock, nhưng tôi không chắc tôi hiểu tại sao.

Andreas được nêu trong comment mình:

Khi [một loại giá trị] đối tượng được truyền từ một thread để người kia, một bản sao được thực hiện, vì vậy các chủ đề kết thúc làm việc trên 2 đối tượng khác nhau, trong đó an toàn.

Có đúng không? Điều đó có nghĩa là khi tôi làm như sau, tôi đang thực tế sửa đổi hai khác nhau x trong xToTrue và phương pháp xToFalse?

public static class Program { 

    public static Boolean x = false; 

    [STAThread] 
    static void Main(string[] args) { 

     var t = new Thread(() => xToTrue()); 
     t.Start(); 
     // ... 
     xToFalse(); 
    } 

    private static void xToTrue() { 
     Program.x = true; 
    } 

    private static void xToFalse() { 
     Program.x = false; 
    } 
} 

(mã này một mình rõ ràng là vô dụng trong trạng thái của nó, nó chỉ là ví dụ)


Tái bút: Tôi biết về câu hỏi này trên How to properly lock a value type. Câu hỏi của tôi không liên quan đến số cách nhưng đến số lý do tại sao.

+0

Chương trình của bạn không chuyển x giữa các chủ đề đang sử dụng bản sao được chia sẻ. Tuy nhiên nó vẫn không an toàn khi bạn không truy cập x trong phạm vi khóa và x không được khai báo dễ bay hơi. Tôi cảm thấy một câu hỏi khác liên quan đến: "Tại sao x trong ví dụ này cần phải dễ bay hơi?" –

+0

@MartinBrown: Tôi biết, và trong mã * thực * của tôi, tôi sử dụng một khóa trên một đối tượng chuyên dụng (như tôi đã đề cập trong câu hỏi của mình, nó liên quan đến * tại sao * chứ không phải * cách *). Đối với 'biến động ', nó là [không cần thiết khi khóa đúng cách] (http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different- part-three.aspx). – Otiel

Trả lời

32

Chỉ cần một đoán hoang dã ở đây ...

nhưng nếu trình biên dịch cho phép bạn khóa trên một loại giá trị, bạn sẽ kết thúc khóa gì cả ... bởi vì mỗi lần bạn thông qua các loại giá trị cho lock , bạn sẽ đi qua một bản sao đóng hộp của nó; một bản sao đóng hộp khác. Vì vậy, các ổ khóa sẽ như thể chúng là những vật thể hoàn toàn khác nhau. (kể từ đó, chúng thực sự là)

Hãy nhớ rằng khi bạn chuyển loại giá trị cho tham số loại object, nó sẽ được đóng hộp (được bao bọc) thành loại tham chiếu. Điều này làm cho nó trở thành một đối tượng hoàn toàn mới mỗi khi điều này xảy ra.

+3

Điểm mấu chốt là loại giá trị sẽ được đóng gói vào một đối tượng khác nhau mỗi lần (boxing không hoàn toàn giống như sao chép và tôi nghĩ cho người đọc OP và người đọc trong tương lai đáng chú ý). Xem [câu trả lời của tôi] (http://stackoverflow.com/q/8267344/593627) –

+0

Đó là một điểm tốt. Tôi sẽ chỉnh sửa. –

+0

Trình biên dịch có thể tạo một loại tham chiếu cố định vô hình khi khóa các loại giá trị. – drowa

15

Nó mở rộng để:

System.Threading.Monitor.Enter(x); 
try { 
    ... 
} 
finally { 
    System.Threading.Monitor.Exit(x); 
} 

Mặc dù họ sẽ biên dịch, Monitor.Enter/Exit đòi hỏi một loại tài liệu tham khảo vì một loại giá trị sẽ được đóng hộp đến một trường hợp đối tượng khác nhau mỗi lần như vậy mỗi cuộc gọi đến EnterExit sẽ hoạt động trên các đối tượng khác nhau.

Từ trang MSDN Enter method:

Sử dụng Monitor để khóa đối tượng (có nghĩa là, các loại tài liệu tham khảo), chứ không phải các loại giá trị. Khi bạn chuyển một biến kiểu giá trị vào Enter, nó được đóng vai trò như một đối tượng. Nếu bạn chuyển cùng một biến vào Enter một lần nữa, nó được đóng hộp như một đối tượng riêng biệt và luồng không bị chặn. Trong trường hợp này, mã mà Màn hình được cho là bảo vệ không được bảo vệ. Hơn nữa, khi bạn chuyển biến sang Exit, vẫn còn một đối tượng riêng biệt khác được tạo ra. Bởi vì đối tượng được truyền vào Exit khác với đối tượng được truyền vào Enter, Monitor ném SynchronizationLockException. Để biết thêm thông tin, hãy xem chủ đề khái niệm Màn hình.

+1

Và tại sao 'Monitor.Enter' và' Monitor.Exit' yêu cầu loại tham chiếu? (có vẻ như câu hỏi rõ ràng để hỏi, xem như đó thực sự là những gì OP là sau). – Oded

+0

Xin lỗi, chỉ cần chỉnh sửa trong. –

+0

Không khi kiểm tra bởi trình biên dịch nó không ... Nó có thể yêu cầu một loại tham chiếu để hoạt động đúng, và nó thậm chí có thể ném một ngoại lệ nếu giá trị là một loại giá trị đóng hộp tôi đã không đã kiểm tra. Nhưng nó chỉ tốt thôi. Việc kiểm tra kiểu tham chiếu được thực hiện bởi trình biên dịch chỉ dành cho câu lệnh lock(). –

2

Vì loại giá trị không có khối đồng bộ hóa mà lệnh khóa sử dụng để khóa trên đối tượng. Chỉ các loại tham chiếu mang chi phí của thông tin loại, khối đồng bộ hóa, v.v.

Nếu bạn đặt kiểu tham chiếu thì bây giờ bạn có đối tượng chứa loại giá trị và có thể khóa đối tượng đó (tôi mong đợi) vì nó hiện có thêm chi phí mà các đối tượng có (một con trỏ đến một khối đồng bộ được sử dụng để khóa, một con trỏ đến thông tin kiểu vv).Như mọi người khác đang nói mặc dù - nếu bạn đóng hộp một đối tượng, bạn sẽ nhận được một đối tượng MỚI mỗi khi bạn hộp nó, do đó bạn sẽ được khóa trên các đối tượng khác nhau mỗi lần - mà hoàn toàn đánh bại mục đích lấy một khóa.

Điều này có lẽ sẽ làm việc (mặc dù nó hoàn toàn vô nghĩa và tôi đã không thử nó)

int x = 7; 
object boxed = (object)x; 

//thread1: 
lock (boxed){ 
... 
} 
//thread2: 
lock(boxed){ 
... 
} 

Chừng nào tất cả mọi người sử dụng đóng hộp và đối tượng đóng hộp chỉ được thiết lập khi bạn có thể sẽ nhận được khóa đúng vì bạn đang khóa trên đối tượng được đóng hộp và nó chỉ được tạo một lần. KHÔNG làm điều này mặc dù .. nó chỉ là một tập thể dục suy nghĩ (và có thể thậm chí không làm việc - như tôi đã nói, tôi đã không thử nghiệm nó).

Đối với câu hỏi thứ hai của bạn - Không, giá trị không được sao chép cho mỗi chuỗi. Cả hai luồng sẽ sử dụng cùng một boolean, nhưng các luồng không được đảm bảo để xem giá trị mới nhất cho nó (khi một luồng đặt giá trị nó có thể không được ghi vào vị trí bộ nhớ ngay lập tức, vì vậy bất kỳ chủ đề nào khác đọc giá trị sẽ nhận được một kết quả 'cũ').

+0

Cảm ơn bạn đã trả lời câu hỏi thứ hai của tôi. Điều đó nghe có vẻ lạ với tôi rằng các giá trị được sao chép cho mỗi luồng. – Otiel

1

Sau đây là lấy từ MSDN:

Các khóa (C#) và SyncLock (Visual Basic) báo cáo có thể được sử dụng để đảm bảo rằng một khối mã chạy đến khi kết thúc mà không bị gián đoạn bởi chủ đề khác. Điều này được thực hiện bằng cách lấy một khóa loại trừ lẫn nhau cho một đối tượng nhất định trong khoảng thời gian của khối mã.

Đối số cung cấp cho các từ khóa khóa phải là một đối tượng dựa trên một loại tài liệu tham khảo, và được sử dụng để xác định phạm vi của khóa.

Tôi giả định rằng điều này một phần vì cơ chế khóa sử dụng một cá thể của đối tượng đó để tạo khóa loại trừ lẫn nhau.

4

Nếu bạn đang yêu cầu khái niệm tại sao điều này là không được phép, tôi sẽ nói câu trả lời xuất phát từ thực tế là một loại giá trị của sắc là chính xác tương đương với giá trị (đó là những gì làm cho nó một giá trị kiểu).

Vì vậy, bất cứ ai bất cứ nơi nào trong vũ trụ nói về int4 đang nói về cùng điều - làm thế nào thì bạn có thể có thể tuyên bố truy cập độc quyền để khóa vào nó?

+0

Đó là một điểm thú vị; đặc biệt nếu ai đó hỏi, "vì một loại giá trị được đóng hộp khi được chuyển đến một tham số đối tượng, tại sao không có khóa cũng chấp nhận các loại giá trị cụ thể ngoài?" ... bởi vì để làm điều đó, khóa sẽ có giá trị - không biến. Điều đó sẽ vô dụng. +1 –

1

Theo điều này MSDN Thread, các thay đổi đối với biến tham chiếu có thể không hiển thị cho tất cả chuỗi và chúng có thể kết thúc bằng giá trị cũ và AFAIK tôi nghĩ rằng loại giá trị sẽ tạo bản sao khi chúng được truyền giữa các chuỗi.

Để trích dẫn chính xác từ MSDN

Nó cũng quan trọng để làm rõ rằng thực tế việc chuyển nhượng là nguyên tử không ngụ ý rằng ghi được ngay lập tức quan sát bởi đề khác. Nếu tham chiếu không biến động, thì có thể một chuỗi khác để đọc giá trị cũ từ tham chiếu một thời gian sau khi chuỗi của bạn đã cập nhật nó. Tuy nhiên, bản cập nhật chính nó là được đảm bảo là nguyên tử (bạn sẽ không thấy một phần của con trỏ cơ bản được cập nhật).

+3

Loại giá trị không được sao chép khi được sử dụng bởi các chủ đề khác nhau. Đó là cùng một địa chỉ trong memry - lý do bạn có thể nhận được các giá trị cũ là giá trị bạn đặt trong một luồng có thể không được ghi lại ngay lập tức vào vị trí bộ nhớ - nó có thể được 'lưu trữ' trong sổ đăng ký vì JIT có thể thấy nó sẽ sớm được sử dụng lại. –

+0

Cảm ơn bạn, tôi không biết rằng, tôi yêu SO – Vamsi

0

Tôi nghĩ đây là một trong những trường hợp mà câu trả lời cho lý do tại sao là "bởi vì một kỹ sư của Microsoft đã triển khai nó theo cách đó".

Cách khóa hoạt động dưới mui xe là tạo bảng cấu trúc khóa trong bộ nhớ và sau đó sử dụng các đối tượng vtable để nhớ vị trí trong bảng có khóa bắt buộc. Điều này mang lại sự xuất hiện mà mọi đối tượng đều có khóa khi thực tế là chúng không có. Chỉ những người đã bị khóa làm. Vì các loại giá trị không có tham chiếu nên không có vtable nào để lưu trữ vị trí khóa.

Tại sao Microsoft chọn cách làm lạ này là đoán của bất kỳ ai. Họ có thể đã thực hiện theo dõi một lớp mà bạn phải khởi tạo. Tôi chắc rằng tôi đã nhìn thấy một bài viết của một nhân viên MS nói rằng trên phản ánh mẫu thiết kế này là một sai lầm, nhưng tôi dường như không thể tìm thấy nó ngay bây giờ.

+2

Tôi tin rằng lý do Microsoft đã làm những việc theo cách đó là một loại khóa tức thời sẽ là: (1) yêu cầu finalizer, (2) tài nguyên bị rò rỉ nếu được nhập nhưng không phải hoặc (3) yêu cầu một số hỗ trợ GC khác để dọn dẹp nếu bị bỏ rơi. Tôi đoán là MS đã quyết định cách tiếp cận # 3 và triển khai nó theo cách mà mọi đối tượng lớp sẽ có chi phí như nhau, và do đó không có trở ngại kỹ thuật cho phép khóa trên mọi đối tượng lớp. – supercat

25

Bạn không thể khóa loại giá trị vì nó không có bản ghi sync root.

Việc khóa được thực hiện bởi cơ chế CLR và hệ điều hành bên trong dựa trên một đối tượng có bản ghi chỉ có thể truy cập được bằng một chuỗi đơn tại gốc - khối đồng bộ hóa. Bất kỳ loại tài liệu tham khảo sẽ có:

  • Con trỏ trỏ tới một kiểu
  • khối Sync gốc
  • Pointer đến thể hiện dữ liệu trong đống
4

tôi đã tự hỏi tại sao đội Net quyết định để hạn chế các nhà phát triển và cho phép Monitor chỉ hoạt động trên tham chiếu. Trước tiên, bạn nghĩ rằng nó sẽ là tốt để khóa chống lại một System.Int32 thay vì xác định một biến đối tượng chuyên dụng chỉ cho mục đích khóa, các tủ khóa không làm bất cứ điều gì khác thường.

Nhưng sau đó dường như bất kỳ tính năng nào được cung cấp bởi ngôn ngữ phải có ngữ nghĩa mạnh mẽ không chỉ hữu ích cho nhà phát triển. Vì vậy, ngữ nghĩa với các kiểu giá trị là bất cứ khi nào một kiểu giá trị xuất hiện trong mã, biểu thức của nó được đánh giá thành một giá trị. Vì vậy, từ quan điểm ngữ nghĩa, nếu chúng ta viết `lock (x) 'và x là một kiểu giá trị nguyên thủy thì nó giống như chúng ta nói" khóa một khối mã quan trọng agaist giá trị của biến x " hơn lạ, chắc chắn :). Trong khi đó, khi chúng ta gặp các biến ref trong mã, chúng ta thường nghĩ "Ồ, nó là một tham chiếu đến một đối tượng" và ngụ ý rằng tham chiếu có thể được chia sẻ giữa các khối mã, các phương thức, các lớp và thậm chí các luồng và các quy trình. bảo vệ.

Trong hai từ, biến loại giá trị chỉ xuất hiện trong mã để được đánh giá theo giá trị thực tế của chúng trong mỗi và mọi biểu thức - không có gì khác.

Tôi đoán đó là một trong những điểm chính.

+2

+1 Giải thích đơn giản và bạn cũng đã trả lời @martin nâu – dotnetguy

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