2013-02-25 22 views
13

ManualResetEventSlim: Gọi .set() ngay sau đó .Reset() không phát hành bất kỳ đề chờ đợiManualResetEventSlim: Calling .set() ngay sau đó .Reset() không phát hành bất kỳ * đề * chờ đợi

(Lưu ý:. Điều này cũng xảy ra với ManualResetEvent, không chỉ với ManualResetEventSlim)

tôi đã thử đoạn code dưới đây trong cả hai phiên bản và debug mode. Tôi đang chạy nó như là một 32-bit xây dựng bằng cách sử dụng .Net 4 trên Windows 7 64-bit chạy trên một bộ xử lý lõi tứ. Tôi biên dịch nó từ Visual Studio 2012 (vì vậy .Net 4.5 được cài đặt).

Sản lượng khi tôi chạy nó trên hệ thống của tôi là:

Waiting for 20 threads to start 
Thread 1 started. 
Thread 2 started. 
Thread 3 started. 
Thread 4 started. 
Thread 0 started. 
Thread 7 started. 
Thread 6 started. 
Thread 5 started. 
Thread 8 started. 
Thread 9 started. 
Thread 10 started. 
Thread 11 started. 
Thread 12 started. 
Thread 13 started. 
Thread 14 started. 
Thread 15 started. 
Thread 16 started. 
Thread 17 started. 
Thread 18 started. 
Thread 19 started. 
Threads all started. Setting signal now. 

0/20 threads received the signal. 

Vì vậy, thiết lập và sau đó ngay lập tức thiết lập lại sự kiện đã không phát hành một chủ đề duy nhất. Nếu bạn bỏ ghi chú Thread.Sleep(), thì tất cả chúng đều được giải phóng.

Điều này có vẻ hơi bất ngờ.

Có ai có lời giải thích không?

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Demo 
{ 
    public static class Program 
    { 
     private static void Main(string[] args) 
     { 
      _startCounter = new CountdownEvent(NUM_THREADS); // Used to count #started threads. 

      for (int i = 0; i < NUM_THREADS; ++i) 
      { 
       int id = i; 
       Task.Factory.StartNew(() => test(id)); 
      } 

      Console.WriteLine("Waiting for " + NUM_THREADS + " threads to start"); 
      _startCounter.Wait(); // Wait for all the threads to have called _startCounter.Signal() 
      Thread.Sleep(100); // Just a little extra delay. Not really needed. 
      Console.WriteLine("Threads all started. Setting signal now."); 
      _signal.Set(); 
      // Thread.Sleep(50); // With no sleep at all, NO threads receive the signal. 
      _signal.Reset(); 
      Thread.Sleep(1000); 
      Console.WriteLine("\n{0}/{1} threads received the signal.\n\n", _signalledCount, NUM_THREADS); 
      Console.WriteLine("Press any key to exit."); 
      Console.ReadKey(); 
     } 

     private static void test(int id) 
     { 
      Console.WriteLine("Thread " + id + " started."); 
      _startCounter.Signal(); 
      _signal.Wait(); 
      Interlocked.Increment(ref _signalledCount); 
      Console.WriteLine("Task " + id + " received the signal."); 
     } 

     private const int NUM_THREADS = 20; 

     private static readonly ManualResetEventSlim _signal = new ManualResetEventSlim(); 
     private static CountdownEvent _startCounter; 
     private static int _signalledCount; 
    } 
} 

Lưu ý: Câu hỏi này có vẻ không có câu trả lời tương tự (ngoài việc xác nhận rằng có, điều này có thể xảy ra).

Issue with ManualResetEvent not releasing all waiting threads consistently


[EDIT]

Như Ian Griffiths chỉ ra dưới đây, câu trả lời là cơ bản Windows API được sử dụng không được thiết kế để hỗ trợ này.

Nó không may rằng the Microsoft documentation for ManualResetEventSlim.Set() bang sai rằng nó

Thiết lập trạng thái của sự kiện để báo hiệu, cho phép một hoặc nhiều đề chờ đợi vào sự kiện để tiến hành.

Rõ ràng "một hoặc nhiều" phải là "không hoặc nhiều hơn".

+0

OK, tôi đã đi. Không ngủ, một sợi nhận được tín hiệu. Với giấc ngủ (0), 8 chủ đề, với giấc ngủ (10), tất cả 20. Vì vậy, các triệu chứng xác nhận. –

+0

Bạn chỉ đang sử dụng đối tượng đồng bộ hóa sai, việc phát hiện xử lý chờ đợi được báo hiệu yêu cầu trình lập lịch trình chuỗi hệ điều hành để chạy. Nó là tồi tệ hơn với phiên bản Slim, nó là mỏng vì nó không thực hiện một cuộc gọi hệ điều hành. Bạn cần Monitor.Pulse() ở đây, lớp Monitor theo dõi các chủ đề chờ đợi. Một tính năng hoàn toàn bị thiếu từ MRE vì nó không thể đảm bảo sự công bằng. Nhưng bạn đã biết rằng từ bản sao được liên kết. –

+0

Chắc chắn, trình lập lịch biểu chạy - Set() là một cuộc gọi hệ thống. –

Trả lời

9

Đặt lại ManualResetEvent không giống như gọi số Monitor.Pulse - nó không đảm bảo rằng nó sẽ phát hành bất kỳ số lượng chủ đề cụ thể nào. Ngược lại, tài liệu (đối với nguyên gốc đồng bộ hóa Win32 cơ bản) khá rõ ràng là bạn không thể biết điều gì sẽ xảy ra:

Bất kỳ số lượng chủ đề chờ đợi, hoặc chủ đề nào bắt đầu hoạt động chờ đợi cho sự kiện được chỉ định đối tượng, có thể được giải phóng trong khi trạng thái của đối tượng được báo hiệu

Cụm từ khóa ở đây là "bất kỳ số nào" bao gồm 0.

Win32 cung cấp số PulseEvent nhưng như nó nói "Chức năng này không đáng tin cậy và không nên sử dụng."Các nhận xét trong tài liệu của nó tại http://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx cung cấp một số thông tin chi tiết về lý do tại sao ngữ nghĩa kiểu xung không thể đạt được một cách đáng tin cậy với một đối tượng sự kiện (Về cơ bản, hạt nhân đôi khi có chủ đề đang chờ đợi một sự kiện trong danh sách chờ của nó tạm thời, vì vậy nó luôn luôn có thể rằng một sợi sẽ bỏ lỡ một 'xung' trên một sự kiện. Đó là sự thật cho dù bạn sử dụng PulseEvent hoặc bạn cố gắng tự mình làm bằng cách đặt và đặt lại sự kiện.)

Ngữ nghĩa dự định của ManualResetEvent là nó hoạt động như một cổng Cổng mở khi bạn đặt nó và đóng khi bạn đặt lại.Nếu bạn mở một cửa và sau đó nhanh chóng đóng nó lại trước khi bất kỳ ai có cơ hội đi qua cánh cổng, bạn sẽ không ngạc nhiên nếu mọi người vẫn tiếp tục mặt sai của cổng, chỉ có những người đủ tỉnh táo để đi qua cánh cổng trong khi bạn mở nó sẽ vượt qua. Đó là cách nó có nghĩa là để làm việc, vì vậy đó là lý do tại sao bạn nhìn thấy những gì bạn nhìn thấy.

Cụ thể ngữ nghĩa của Set là rất nhiều không phải "cổng mở và đảm bảo tất cả các chuỗi đang chờ được thông qua cổng". (Và nếu nó có nghĩa là, nó không phải là rõ ràng những gì hạt nhân nên làm gì với đa đối tượng chờ đợi.) Vì vậy, đây không phải là một "vấn đề" trong ý nghĩa rằng sự kiện này không có nghĩa là để được sử dụng theo cách bạn đang cố gắng sử dụng nó, vì vậy nó hoạt động chính xác. Nhưng nó là một vấn đề trong ý nghĩa rằng bạn sẽ không thể sử dụng điều này để có được hiệu quả mà bạn đang tìm kiếm. (Đó là một nguyên thủy hữu ích, nó chỉ là không hữu ích cho những gì bạn đang cố gắng làm. Tôi có xu hướng sử dụng ManualResetEvent dành riêng cho cửa ban đầu đóng cửa, và được mở chính xác một lần, và không bao giờ được đóng lại một lần nữa.)

Vì vậy, bạn có thể cần phải xem xét một số nguyên tắc đồng bộ hóa khác.

+0

Tôi đang sử dụng Monitor.PulseAll() trong mã btw thực tế của mình. Tài liệu hướng dẫn cho ManualResetEventSlim.Set() cho biết: * "Đặt trạng thái của sự kiện thành tín hiệu, cho phép ** một hoặc nhiều ** chủ đề chờ sự kiện tiến hành" *, trên khuôn mặt của nó, chỉ là sai rồi. Nó sẽ nói * không hoặc nhiều hơn *. –

+0

Việc sử dụng 'bất kỳ' trong tài liệu MSDN có thể được hiểu là tạo sự khác biệt giữa MRE và ARE. Nếu 40 chủ đề được xếp hàng đợi trên một MRE, báo hiệu nó sẽ làm cho tất cả 40 đã sẵn sàng. Nếu không, nó bị hỏng. –

+1

Tôi đoán câu trả lời là "Nó không hoạt động vì nó không được thiết kế để hoạt động theo cách đó và tài liệu của Microsoft cho ManualResetEvent.Set() là không chính xác và không đầy đủ". –

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