Giám sát và khóa


88

Khi nào thì thích hợp để sử dụng Monitorlớp hoặc locktừ khóa cho an toàn luồng trong C #?

CHỈNH SỬA: Có vẻ như từ các câu trả lời cho đến nay đó locklà bàn tay ngắn cho một loạt các cuộc gọi đến Monitorlớp. Chính xác thì khóa gọi tắt là gì? Hay rõ ràng hơn,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Cập nhật

Cảm ơn tất cả sự giúp đỡ của bạn: Tôi đã đăng một câu hỏi khác như một phần tiếp theo một số thông tin mà tất cả các bạn đã cung cấp. Vì bạn có vẻ thành thạo trong lĩnh vực này, tôi đã đăng liên kết: Giải pháp khóa và quản lý các ngoại lệ bị khóa này có gì sai?

Câu trả lời:


89

Eric Lippert nói về điều này trong blog của anh ấy: Khóa và ngoại lệ không trộn lẫn

Mã tương đương khác nhau giữa C # 4.0 và các phiên bản trước đó.


Trong C # 4.0, nó là:

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

Nó dựa vào Monitor.Enterviệc thiết lập cờ một cách nguyên tử khi khóa được thực hiện.


Và trước đó nó là:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Điều này không phụ thuộc vào việc không có ngoại lệ được ném giữa Monitor.Entertry. Tôi nghĩ rằng trong mã gỡ lỗi, điều kiện này đã bị vi phạm bởi vì trình biên dịch đã chèn một NOP giữa chúng và do đó có thể thực hiện phá thai luồng giữa chúng.


Như tôi đã nêu, ví dụ đầu tiên là C # 4 và ví dụ còn lại là những gì các phiên bản trước đó sử dụng.
CodesInChaos

Lưu ý thêm, C # qua CLR đề cập đến một cảnh báo của từ khóa khóa: bạn có thể thường muốn làm điều gì đó để khôi phục trạng thái bị hỏng (nếu có) trước khi giải phóng khóa. Vì từ khóa lock không cho phép chúng ta đưa mọi thứ vào khối catch, chúng ta nên cân nhắc việc viết phiên bản dài try-catch-last cho các quy trình không tầm thường.
kizzx2

5
IMO khôi phục trạng thái chia sẻ là trực giao với khóa / đa luồng. Vì vậy, nó nên được thực hiện với một lần thử bắt / cuối cùng bên trong lockkhối.
CodesInChaos

2
@ kizzx2: Một mô hình như vậy sẽ đặc biệt đẹp với ổ khóa người viết-người đọc. Nếu một ngoại lệ xảy ra trong mã giữ khóa trình đọc, không có lý do gì để mong đợi rằng tài nguyên được bảo vệ có thể bị hỏng và do đó không có lý do gì để làm mất hiệu lực của nó. Nếu một ngoại lệ xảy ra trong một khóa ghi và mã xử lý ngoại lệ không biểu thị rõ ràng rằng trạng thái của đối tượng được bảo vệ đã được sửa chữa, điều đó cho thấy rằng đối tượng có thể bị hỏng và nên bị vô hiệu. IMHO, các ngoại lệ không mong muốn sẽ không làm hỏng chương trình, nhưng sẽ làm mất hiệu lực của bất kỳ thứ gì có thể bị hỏng.
supercat

2
@ArsenZahray Bạn không cần Pulsekhóa đơn giản. Nó quan trọng trong một số trường hợp đa luồng nâng cao. Tôi chưa bao giờ sử dụng Pulsetrực tiếp.
CodesInChaos

43

lockchỉ là phím tắt cho Monitor.Entervới try+ finallyMonitor.Exit. Sử dụng câu lệnh khóa bất cứ khi nào là đủ - nếu bạn cần một cái gì đó như TryEnter, bạn sẽ phải sử dụng Màn hình.


23

Một câu lệnh khóa tương đương với:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Tuy nhiên, hãy nhớ rằng Màn hình cũng có thể Wait ()Pulse () , thường hữu ích trong các tình huống đa luồng phức tạp.

Cập nhật

Tuy nhiên trong C # 4, nó được triển khai khác:

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

Thanx tới CodeInChaos để nhận xét và liên kết


Trong C # 4, câu lệnh lock được thực hiện theo cách khác. blogs.msdn.com/b/ericlippert/archive/2009/03/06/...
CodesInChaos

14

Monitorlinh hoạt hơn. Trường hợp sử dụng yêu thích của tôi khi sử dụng màn hình là khi bạn không muốn đợi đến lượt và chỉ cần bỏ qua :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}

6

Như những người khác đã nói, lock"tương đương" với

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Nhưng chỉ vì tò mò, locksẽ giữ nguyên tham chiếu đầu tiên mà bạn chuyển cho nó và sẽ không ném nếu bạn thay đổi nó. Tôi biết không nên thay đổi đối tượng bị khóa và bạn không muốn làm điều đó.

Nhưng một lần nữa, đối với khoa học, điều này hoạt động tốt:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... Và điều này không:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Lỗi:

Một ngoại lệ của loại 'System.Threading.SynchronizationLockException' đã xảy ra trong 70783sTUDIES.exe nhưng không được xử lý trong mã người dùng

Thông tin bổ sung: Phương thức đồng bộ hóa đối tượng được gọi từ một khối mã không đồng bộ hóa.

Điều này là do Monitor.Exit(lockObject);sẽ hoạt động trên lockObjectđó đã thay đổi vì stringskhông thay đổi được, khi đó bạn đang gọi nó từ một khối mã không đồng bộ hóa .. nhưng dù sao. Đây chỉ là một sự thật thú vị.


"Điều này là do Monitor.Exit (lockObject); sẽ hoạt động trên lockObject". Sau đó, khóa không có gì với đối tượng? Khóa hoạt động như thế nào?
Yugo Amaryl

@YugoAmaryl, tôi cho rằng đó là vì tuyên bố khóa giữ trong tâm trí tham khảo đầu tiên trôi qua và sau đó sử dụng nó thay vì sử dụng thay đổi tài liệu tham khảo, như:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.

3

Cả hai đều giống nhau. lock là từ khóa c sharp và sử dụng lớp Monitor.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx


3
Nhìn vào msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "Thực tế, từ khóa khóa được triển khai với lớp Màn hình. Ví dụ:"
RobertoBr

1
việc triển khai cơ bản của khóa sử dụng Màn hình nhưng chúng không giống nhau, hãy xem xét các phương pháp được cung cấp bởi màn hình không tồn tại cho khóa và cách bạn có thể khóa và mở khóa trong các khối mã riêng biệt.
eran otzap

3

Khóa và hoạt động cơ bản của màn hình (vào + ra) ít nhiều giống nhau, nhưng màn hình có nhiều tùy chọn hơn cho phép bạn nhiều khả năng đồng bộ hóa hơn.

Khóa là một phím tắt và là tùy chọn cho cách sử dụng cơ bản.

Nếu bạn cần kiểm soát nhiều hơn, màn hình là lựa chọn tốt hơn. Bạn có thể sử dụng Wait, TryEnter và Pulse, cho các cách sử dụng nâng cao (như rào cản, semaphores, v.v.).


1

Khóa Khóa từ khóa đảm bảo rằng một thread đang thực hiện một đoạn mã cùng một lúc.

khóa (lockObject)

        {
        //   Body
        }

Từ khóa lock đánh dấu một khối câu lệnh là phần quan trọng bằng cách lấy khóa loại trừ lẫn nhau cho một đối tượng nhất định, thực hiện một câu lệnh và sau đó giải phóng khóa

Nếu một luồng khác cố gắng nhập mã bị khóa, nó sẽ đợi, chặn, cho đến khi đối tượng được giải phóng.

Màn hình Màn hình là một lớp tĩnh và thuộc không gian tên System.Threading.

Nó cung cấp khóa độc quyền trên đối tượng để chỉ một luồng có thể đi vào phần quan trọng tại bất kỳ thời điểm nhất định nào.

Sự khác biệt giữa Màn hình và khóa trong C #

Khóa là phím tắt cho Monitor.Enter với thử và cuối cùng. Khóa tay cầm thử và cuối cùng chặn nội bộ Khóa = Màn hình + thử cuối cùng.

Nếu bạn muốn kiểm soát nhiều hơn để thực hiện các giải pháp đa luồng cao cấp sử dụng TryEnter() Wait(), Pulse()PulseAll()phương pháp, sau đó các lớp Monitor là lựa chọn của bạn.

C # Monitor.wait(): Một luồng chờ các luồng khác thông báo.

Monitor.pulse(): Một luồng thông báo cho một luồng khác.

Monitor.pulseAll(): Một chuỗi thông báo cho tất cả các chuỗi khác trong một quy trình


0

Ngoài tất cả các giải thích ở trên, lock là một câu lệnh C # trong khi Monitor là một lớp .NET nằm trong không gian tên System.Threading.

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.