2009-10-13 24 views
41

Tôi gặp khó khăn trong việc tìm hiểu Wait(), Pulse(), PulseAll(). Tất cả chúng có tránh được bế tắc không? Tôi sẽ đánh giá cao nếu bạn giải thích cách sử dụng chúng?C#: Màn hình - Chờ, Xung, PulseAll

Trả lời

48

phiên bản ngắn:

lock(obj) {...} 

là ngắn tay cho Monitor.Enter/Monitor.Exit (với xử lý ngoại lệ vv). Nếu không ai khác có khóa, bạn có thể nhận được nó (và chạy mã của bạn) - nếu không thread của bạn bị chặn cho đến khi khóa được aquired (bởi một thread phát hành nó).

Deadlock thường xảy ra khi một trong hai A: hai luồng khóa điều trong đơn đặt hàng khác nhau:

thread 1: lock(objA) { lock (objB) { ... } } 
thread 2: lock(objB) { lock (objA) { ... } } 

(ở đây, nếu họ từng được các khóa đầu tiên, không phải bao giờ có thể nhận được thứ hai, vì không chủ đề có thể exit để giải phóng khóa của họ)

Kịch bản này có thể được giảm thiểu bằng cách luôn khóa theo thứ tự tương tự; và bạn có thể phục hồi (ở mức độ) bằng cách sử dụng Monitor.TryEnter (thay vì Monitor.Enter/lock) và chỉ định thời gian chờ.

hoặc B: bạn có thể chặn chính mình với những thứ như winforms khi thread-chuyển mạch trong khi giữ một khóa:

lock(obj) { // on worker 
    this.Invoke((MethodInvoker) delegate { // switch to UI 
     lock(obj) { // oopsiee! 
      ... 
     } 
    }); 
} 

Các bế tắc xuất hiện rõ ràng ở trên, nhưng nó không phải là quá rõ ràng khi bạn có mì spaghetti mã; câu trả lời có thể: không chuyển đổi chuỗi trong khi giữ khóa hoặc sử dụng BeginInvoke để ít nhất bạn có thể thoát khỏi khóa (cho phép chơi giao diện người dùng).


Wait/Pulse/PulseAll là khác nhau; chúng dành cho tín hiệu.Tôi sử dụng in this answer này để báo hiệu để:

  • Dequeue: nếu bạn cố gắng dequeue dữ liệu khi hàng đợi rỗng, nó chờ đợi một thread để thêm dữ liệu, trong đó tỉnh dậy thread chặn
  • Enqueue: nếu bạn cố gắng và enqueue dữ liệu khi hàng đợi đầy, nó chờ đợi một thread để xóa dữ liệu, trong đó tỉnh dậy thread chặn

Pulse chỉ tỉnh dậy một chủ đề - nhưng tôi không đủ thông minh để chứng minh rằng chủ đề tiếp theo tôi tôi luôn là người tôi muốn, vì vậy tôi có xu hướng sử dụng PulseAll và chỉ cần xác minh lại các điều kiện trước khi tiếp tục; là một ví dụ:

 while (queue.Count >= maxSize) 
     { 
      Monitor.Wait(queue); 
     } 

Với phương pháp này, tôi có thể yên tâm thêm ý nghĩa khác của Pulse, mà không cần mã hiện tại của tôi giả định rằng "Tôi tỉnh dậy, do đó có dữ liệu" - đó là tiện dụng khi (trong ví dụ tương tự) Sau đó tôi cần thêm phương thức Close().

1

Đọc Jon Skeet's multi-part threading article.

Thực sự tốt. Những điều bạn đề cập đến là khoảng một phần ba con đường.

+7

thậm chí không đề cập đến Pulse hoặc Wait! Liên kết tới bài viết của John Skeet không tự động đưa ra câu trả lời hay ... –

+2

Oh really? Cái gì đây? 'http: // www.yoda.arachsys.com/csharp/threads/deadlocks.shtml' – Geo

+0

@Geo: vâng, cái này phù hợp hơn;) –

7

Không, chúng không bảo vệ bạn khỏi bị bế tắc. Chúng chỉ là các công cụ linh hoạt hơn cho đồng bộ hóa luồng. Đây là một lời giải thích rất tốt về cách sử dụng chúng và mô hình sử dụng rất quan trọng - không có mẫu này, bạn sẽ phá vỡ mọi thứ: http://www.albahari.com/threading/part4.aspx

+1

CHO NHỮNG NOVICES HIGHOM RECOMMEND trang albahari là hoàn hảo! Đi qua luồng và đồng bộ từng bước với các ví dụ rõ ràng. – DanG

1

Chúng là công cụ để đồng bộ hóa và báo hiệu giữa các chủ đề. Như vậy họ không làm gì để ngăn chặn deadlocks, nhưng nếu được sử dụng một cách chính xác họ có thể được sử dụng để đồng bộ hóa và giao tiếp giữa các chủ đề.

Thật không may, hầu hết công việc cần thiết để viết mã đa luồng chính xác hiện là trách nhiệm của nhà phát triển trong C# (và nhiều ngôn ngữ khác). Hãy xem F #, Haskell và Clojure xử lý điều này như thế nào cho một cách tiếp cận hoàn toàn khác.

37

Công thức đơn giản để sử dụng Monitor.Wait và Monitor.Pulse. Nó bao gồm một công nhân, một ông chủ, và một chiếc điện thoại họ sử dụng để giao tiếp:

object phone = new object(); 

A "Công nhân" chủ đề:

lock(phone) // Sort of "Turn the phone on while at work" 
{ 
    while(true) 
    { 
     Monitor.Wait(phone); // Wait for a signal from the boss 
     DoWork(); 
     Monitor.PulseAll(phone); // Signal boss we are done 
    } 
} 

A "Boss" chủ đề:

PrepareWork(); 
lock(phone) // Grab the phone when I have something ready for the worker 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    Monitor.Wait(phone); // Wait for the work to be done 
} 

Các ví dụ phức tạp khác theo sau ...

"Công nhân có việc khác cần làm":

lock(phone) 
{ 
    while(true) 
    { 
     if(Monitor.Wait(phone,1000)) // Wait for one second at most 
     { 
      DoWork(); 
      Monitor.PulseAll(phone); // Signal boss we are done 
     } 
     else 
      DoSomethingElse(); 
    } 
} 

An "Nóng lòng chờ đợi Boss":

PrepareWork(); 
lock(phone) 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    if(Monitor.Wait(phone,1000)) // Wait for one second at most 
     Console.Writeline("Good work!"); 
} 
+0

Tôi không hiểu. Làm thế nào có thể là Boss và công nhân đang trong khóa "điện thoại" cùng một lúc? – Marshall

+1

@Marshall Monitor.Wait thả khóa "điện thoại" sang dòng tiếp theo trong dòng (có lẽ là với Boss). –

+0

@DennisGorelik ahh, tôi hiểu rồi. Tôi đã mở rộng quan điểm của bạn [dưới đây] (http://stackoverflow.com/a/42581381/1282864) – jdpilgrim

1

Thật không may, none của Chờ(), Pulse() hoặc PulseAll() có thuộc tính kỳ diệu mà bạn đang có nhu cầu cho - đó là bằng cách sử dụng API này, bạn sẽ tự động tránh bế tắc.

Xét đoạn mã sau

object incomingMessages = new object(); //signal object 

LoopOnMessages() 
{ 
    lock(incomingMessages) 
    { 
     Monitor.Wait(incomingMessages); 
    } 
    if (canGrabMessage()) handleMessage(); 
    // loop 
} 

ReceiveMessagesAndSignalWaiters() 
{ 
    awaitMessages(); 
    copyMessagesToReadyArea(); 
    lock(incomingMessages) { 
     Monitor.PulseAll(incomingMessages); //or Monitor.Pulse 
    } 
    awaitReadyAreaHasFreeSpace(); 
} 

Mã này sẽ bế tắc! Có lẽ không phải hôm nay, có thể không phải ngày mai. Rất có thể khi mã của bạn được đặt dưới sự căng thẳng bởi vì đột nhiên nó đã trở nên phổ biến hoặc quan trọng, và bạn đang được gọi để sửa chữa một vấn đề khẩn cấp.

Tại sao?

Cuối cùng sau đây sẽ xảy ra:

  1. Tất cả các chủ đề của người tiêu dùng đang làm một số công việc
  2. Tin nhắn đến, diện tích sẵn sàng không thể giữ bất kỳ tin nhắn nhiều hơn, và PulseAll() được gọi.
  3. Không người tiêu dùng bị đánh thức, vì không đang chờ đợi
  4. Tất cả các chủ đề người tiêu dùng gọi Chờ() [bế tắc]

ví dụ cụ thể này giả định rằng nhà sản xuất đề là không bao giờ gọi PulseAll() một lần nữa bởi vì nó không có nhiều không gian để đưa thông điệp vào. Nhưng có rất nhiều, nhiều, nhiều biến thể bị hỏng trên mã này có thể được. Mọi người sẽ cố gắng để làm cho nó mạnh mẽ hơn bằng cách thay đổi một dòng như làm Monitor.Wait(); vào

if (!canGrabMessage()) Monitor.Wait(incomingMessages); 

Thật không may, điều đó vẫn là chưa đủ để sửa chữa nó. Để khắc phục nó, bạn cũng cần phải thay đổi phạm vi khóa nơi Monitor.PulseAll() được gọi là:

LoopOnMessages() 
{ 
    lock(incomingMessages) 
    { 
     if (!canGrabMessage()) Monitor.Wait(incomingMessages); 
    } 
    if (canGrabMessage()) handleMessage(); 
    // loop 
} 

ReceiveMessagesAndSignalWaiters() 
{ 
    awaitMessagesArrive(); 
    lock(incomingMessages) 
    { 
     copyMessagesToReadyArea(); 
     Monitor.PulseAll(incomingMessages); //or Monitor.Pulse 
    } 
    awaitReadyAreaHasFreeSpace(); 
} 

Điểm mấu chốt là trong mã cố định, ổ khóa hạn chế trình tự có thể xảy ra sự kiện:

  1. Một đề người tiêu dùng thực hiện công việc của mình và vòng

  2. Đó chủ đề mua lại các khóa

    Và nhờ khóa, điều này đúng là hoặc:

  3. a. Tin nhắn chưa đến trong khu vực sẵn sàng, và nó ra mắt khóa bằng cách gọi Chờ() TRƯỚC thread nhắn nhận có thể có được khóa và sao chép nhiều thông điệp vào khu vực sẵn sàng, hoặc

    b. Tin nhắn có đã đến trong khu vực đã sẵn sàng và nó nhận được tin nhắn INSTEAD OF đang gọi Wait(). (Và trong khi nó đang đưa ra quyết định này là không thể đối với các chuỗi tin nhắn nhận để ví dụ được các khóa và sao chép nhiều thông điệp vào khu vực sẵn sàng.)

Kết quả là vấn đề của mã ban đầu tại không bao giờ xảy ra : 3. Khi PulseEvent() được gọi Không người tiêu dùng bị đánh thức, vì không đang chờ đợi

Bây giờ quan sát thấy trong mã này bạn có để có được phạm vi khóa chính xác đúng. (Nếu thực sự tôi đã nhận nó đúng!)

Và cũng có thể, kể từ khi bạn phải sử dụng lock (hoặc Monitor.Enter() vv) để sử dụng Monitor.PulseAll() hoặc Monitor.Wait() một cách bế tắc-miễn phí, bạn vẫn phải lo lắng về khả năng của khác khóa chết xảy ra do khóa đó.

Bottom line: các API này cũng dễ dàng để vít lên và bế tắc với, tức là khá nguy hiểm

0

Cái gì mà tổng ném tôi ở đây là Pulse chỉ đưa ra một "người đứng đầu lên" to a thread trong một Wait .Chuỗi chờ sẽ không tiếp tục cho đến khi chuỗi đã làm Pulsebỏ khóa và chuỗi đang chờ thắng thành công.

lock(phone) // Grab the phone 
{ 
    Monitor.PulseAll(phone); // Signal worker 
    Monitor.Wait(phone); // ****** The lock on phone has been given up! ****** 
} 

hoặc

lock(phone) // Grab the phone when I have something ready for the worker 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    DoMoreWork(); 
} // ****** The lock on phone has been given up! ****** 

Trong cả hai trường hợp nó không phải cho đến khi "khóa trên điện thoại đã được đưa lên" mà thread khác có thể lấy nó.

Có thể có các chủ đề khác đang chờ khóa đó từ Monitor.Wait(phone) hoặc lock(phone). Chỉ người thắng khóa sẽ tiếp tục.

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