Làm thế nào để khóa hoạt động chính xác?


527

Tôi thấy rằng để sử dụng các đối tượng không phải là luồng an toàn, chúng tôi bọc mã bằng một khóa như thế này:

private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

Vì vậy, điều gì xảy ra khi nhiều luồng truy cập vào cùng một mã (giả sử rằng nó đang chạy trong một ứng dụng web ASP.NET). Họ có xếp hàng không? Nếu vậy họ sẽ đợi bao lâu?

Tác động hiệu suất vì sử dụng ổ khóa là gì?


Câu trả lời:


448

Câu locklệnh được dịch bởi C # 3.0 như sau:

var temp = obj;

Monitor.Enter(temp);

try
{
    // body
}
finally
{
    Monitor.Exit(temp);
}

Trong C # 4.0, điều này đã thay đổi và hiện được tạo như sau:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}

Bạn có thể tìm thêm thông tin về những gì Monitor.Enterlàm ở đây . Để trích dẫn MSDN:

Sử dụng Enterđể thu được Màn hình trên đối tượng được truyền dưới dạng tham số. Nếu một luồng khác đã thực thi một Enter đối tượng nhưng chưa thực hiện tương ứng Exit, thì luồng hiện tại sẽ chặn cho đến khi luồng khác giải phóng đối tượng. Nó là hợp pháp cho cùng một chủ đề để gọi Enternhiều lần mà không bị chặn; tuy nhiên, một số lượng Exitcuộc gọi bằng nhau phải được gọi trước khi các luồng khác đang chờ trên đối tượng sẽ bỏ chặn.

Các Monitor.Enterphương pháp sẽ chờ đợi vô hạn; nó sẽ không hết thời gian


15
Theo MSDN "Sử dụng từ khóa khóa (C #) hoặc SyncLock (Visual Basic) thường được ưu tiên hơn so với sử dụng trực tiếp lớp Monitor, vì cả khóa hoặc SyncLock đều ngắn gọn hơn và vì khóa hoặc SyncLock bảo đảm rằng màn hình bên dưới được phát hành, thậm chí nếu mã được bảo vệ ném ra một ngoại lệ. Điều này được thực hiện với từ khóa cuối cùng, thực thi khối mã liên quan của nó bất kể có ném ngoại lệ hay không. " msdn.microsoft.com/en-us/l Library / ms173179.aspx
Aiden Strydom

10
Điểm của var temp = obj; hàng. vì nó chỉ là một ref để bắt đầu, làm cho một người khác làm gì tốt?
priehl

11
@priehl Nó cho phép người dùng thay đổi objmà không khiến toàn bộ hệ thống bế tắc.
Steven

7
@Joymon cuối cùng, mọi tính năng ngôn ngữ là cú pháp đường. Các tính năng ngôn ngữ là về việc làm cho các nhà phát triển làm việc hiệu quả hơn và làm cho các ứng dụng dễ bảo trì hơn, cũng như tính năng khóa.
Steven

2
Chính xác. Đây là toàn bộ mục đích của lock-statement và Monitor: để bạn có thể thực hiện một thao tác trong một luồng mà không phải lo lắng về một luồng khác làm hỏng nó.
Chóng mặt H. Muffin

285

Nó đơn giản hơn bạn nghĩ.

Theo Microsoft : lockTừ khóa đảm bảo rằng một luồng không nhập một phần quan trọng của mã trong khi một luồng khác nằm trong phần quan trọng. Nếu một luồng khác cố gắng nhập mã bị khóa, nó sẽ chờ, chặn, cho đến khi đối tượng được giải phóng.

Các locktừ khóa gọi Enterở đầu khối và Exitở cuối khối. locktừ khóa thực sự xử lý Monitorlớp ở cuối.

Ví dụ:

private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

Trong đoạn mã trên, đầu tiên luồng vào một phần quan trọng, và sau đó nó sẽ khóa obj. Khi một luồng khác cố gắng nhập, nó cũng sẽ cố gắng khóa obj, luồng này đã bị khóa bởi luồng đầu tiên. Chủ đề thứ hai sẽ phải chờ cho chủ đề đầu tiên phát hành obj. Khi luồng đầu tiên rời đi, sau đó một luồng khác sẽ khóa objvà sẽ vào phần quan trọng.


9
chúng ta nên tạo một đối tượng giả để khóa hoặc chúng ta có thể khóa một biến hiện có trong ngữ cảnh không?
batmaci

9
@batmaci - Khóa trên một đối tượng giả riêng tư mang đến cho bạn một sự đảm bảo rằng không ai khác đang khóa trong đối tượng đó. Nếu bạn khóa dữ liệu và cùng một phần dữ liệu sẽ hiển thị ra bên ngoài, bạn sẽ mất sự đảm bảo đó.
Umar Abbas

8
Điều gì xảy ra nếu có nhiều hơn một quá trình đang chờ khóa phát hành? Các quy trình chờ có được xếp hàng để chúng sẽ khóa phần quan trọng theo thứ tự FIFO không?
jstuardo

@jstuardo - Họ đang xếp hàng, nhưng thứ tự không được đảm bảo là FIFO. Kiểm tra liên kết này: albahari.com/threading/part2.aspx
Umar Abbas


47

Không, họ không được xếp hàng, họ đang ngủ

Một tuyên bố khóa của mẫu

lock (x) ... 

Trong đó x là biểu thức của kiểu tham chiếu, chính xác tương đương với

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

Bạn chỉ cần biết rằng họ đang đợi nhau và chỉ một luồng sẽ nhập vào khối khóa, những người khác sẽ đợi ...

Màn hình được viết hoàn toàn bằng .net vì vậy nó đủ nhanh, cũng nhìn vào lớp Monitor với gương phản xạ để biết thêm chi tiết


6
Lưu ý rằng mã phát ra cho các locktuyên bố thay đổi một chút trong C # 4: blogs.msdn.com/b/ericlippert/archive/2009/03/06/...
LukeH

@ArsenMkrt, chúng không được giữ ở trạng thái "Bị chặn" "Hàng đợi? Tôi nghĩ có một số khác biệt giữa trạng thái Ngủ và khối, phải không?
Mohanavel

bạn có ý nghĩa gì khác @Mohanavel?
Asen Mkrtchyan

1
Đó không phải là câu hỏi. Câu hỏi liên quan đến từ khóa "khóa". Giả sử một quá trình nhập phần "khóa". Điều đó có nghĩa là quy trình chặn đoạn mã đó và không có quy trình nào khác có thể vào phần đó cho đến khi khóa đó được giải phóng. Chà .... bây giờ, 2 quá trình nữa cố gắng vào cùng một khối. Vì nó được bảo vệ bởi từ khóa "khóa", họ sẽ chờ đợi, theo những gì được nói trong diễn đàn này. Khi quá trình đầu tiên phát hành khóa. Quá trình nào đi vào khối? cái đầu tiên cố gắng nhập hay cái cuối cùng?
jstuardo

1
Tôi đoán bạn có nghĩa là chủ đề thay vì xử lý ... nếu vậy, hơn câu trả lời là Không, không có gì đảm bảo ai sẽ nhập ... thêm ở đây stackoverflow.com/questions/4228864/
Lỗi

29

Khóa sẽ chặn các luồng khác thực thi mã có trong khối khóa. Các luồng sẽ phải đợi cho đến khi luồng trong khối khóa hoàn thành và khóa được giải phóng. Điều này có tác động tiêu cực đến hiệu suất trong môi trường đa luồng. Nếu bạn cần phải làm điều này, bạn nên đảm bảo mã trong khối khóa có thể xử lý rất nhanh. Bạn nên cố gắng tránh các hoạt động đắt tiền như truy cập cơ sở dữ liệu, v.v.


11

Tác động hiệu suất phụ thuộc vào cách bạn khóa. Bạn có thể tìm thấy một danh sách tối ưu hóa tốt ở đây: http://www.thinkingabul.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

Về cơ bản, bạn nên cố gắng khóa càng ít càng tốt, vì nó đặt mã chờ của bạn vào trạng thái ngủ. Nếu bạn có một số tính toán nặng hoặc mã kéo dài (ví dụ như tải tệp lên), nó sẽ dẫn đến mất hiệu suất rất lớn.


1
Nhưng cố gắng viết mã khóa thấp thường có thể dẫn đến các lỗi tinh vi, khó tìm và sửa, ngay cả khi bạn là một chuyên gia trong lĩnh vực này. Sử dụng khóa thường là ít tệ hơn của hai tệ nạn. Bạn nên khóa chính xác bao nhiêu tùy ý, không hơn, không kém!
LukeH

1
@LukeH: Có một số kiểu sử dụng trong đó mã khóa thấp có thể rất đơn giản và dễ dàng [ do { oldValue = thing; newValue = updated(oldValue); } while (CompareExchange(ref thing, newValue, oldValue) != oldValue]. Mối nguy hiểm lớn nhất là nếu các yêu cầu phát triển vượt quá những gì các kỹ thuật như vậy có thể xử lý, có thể khó điều chỉnh mã để xử lý điều đó.
supercat

Liên kết đã bị hỏng.
CarenRose

8

Phần trong câu lệnh khóa chỉ có thể được thực thi bởi một luồng, vì vậy tất cả các luồng khác sẽ chờ vô thời hạn cho nó, luồng giữ khóa kết thúc. Điều này có thể dẫn đến cái gọi là bế tắc.


8

Các locktuyên bố được dịch để gọi đến EnterExitphương thức của Monitor.

Các locktuyên bố sẽ chờ đợi vô thời hạn cho đối tượng khóa sẽ được phát hành.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.