2013-06-23 55 views
7

tôi thấy mã này vào cuốn sách Richter:Khởi tạo hẹn giờ và điều kiện Race trong C#?

Đoạn mã dưới đây trình bày cách để có một thread hồ bơi gọi một phương pháp bắt đầu ngay lập tức và sau đó mỗi 2 giây sau đó:

/*1*/ internal static class TimerDemo 
/*2*/ { 
/*3*/  private static Timer s_timer; 
/*4*/  public static void Main() 
/*5*/  { 
/*6*/   Console.WriteLine("Checking status every 2 seconds"); 
/*7*/   // Create the Timer ensuring that it never fires. This ensures that 
/*8*/   // s_timer refers to it BEFORE Status is invoked by a thread pool thread 
/*9*/   s_timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite); 
/*10*/   // Now that s_timer is assigned to, we can let the timer fire knowing 
/*11*/   // that calling Change in Status will not throw a NullReferenceException 
/*12*/   s_timer.Change(0, Timeout.Infinite); 
/*13*/   Console.ReadLine(); // Prevent the process from terminating 
/*14*/  } 
/*15*/  // This method's signature must match the TimerCallback delegate 
/*16*/  private static void Status(Object state) 
/*17*/  { 
/*18*/   // This method is executed by a thread pool thread 
/*20*/   Console.WriteLine("In Status at {0}", DateTime.Now); 
/*21*/   Thread.Sleep(1000); // Simulates other work (1 second) 
/*22*/   // Just before returning, have the Timer fire again in 2 seconds 
/*23*/   s_timer.Change(2000, Timeout.Infinite); 
/*24*/   // When this method returns, the thread goes back 
/*25*/   // to the pool and waits for another work item 
/*26*/  } 
/*27*/ } 

Tuy nhiên , (xin lỗi), tôi vẫn không hiểu dòng nào #7,#8 nghĩa là

Và dĩ nhiên - tại sao nó được khởi tạo (dòng # 9) thành Timeout.Infinite (Mà rõ ràng là: "không bắt đầu hẹn giờ")

(Tôi làm hiểu được mục đích chung để ngăn ngừa sự chồng chéo, nhưng tôi tin rằng đó cũng là một GC đua điều kiện pov ở đây.)

chỉnh sửa

không gian tên là System.Threading

+0

Để ngăn chặn cuộc gọi giữa lần gọi lại 'Trạng thái' được thực hiện lần đầu tiên và gán cho' s_timer', nó không liên quan đến GC. – Lee

+0

Và nó thực sự thông minh, tôi sẽ không bao giờ có mặc dù để thủ đoạn như vậy. Kudos để * Jeffrey Richter *! – Pragmateek

+0

@Lee edited .... –

Trả lời

11

tôi nghĩ rằng nó không liên quan đến GC mà là để tránh tình trạng đua:

Thao tác gán không phải là nguyên tử: trước hết bạn tạo đối tượng Timer, sau đó bạn gán nó.

Vì vậy, đây là một kịch bản:

  • new Timer(...) tạo bộ đếm thời gian và nó bắt đầu "đếm"

  • thread hiện hành được preempted TRƯỚC nhiệm vụ kết thúc =>s_timer vẫn rỗng

  • bộ hẹn giờ đánh thức trên một chủ đề khác và gọi Status nhưng chuỗi ban đầu có chưa hoàn thành thao tác gán!

  • Status truy cập s_timer mà là một null tham chiếu =>BOOM!

Với phương pháp của mình, điều đó không thể xảy ra, ví dụ:với cùng một kịch bản:

  • hẹn giờ đã được tạo ra nhưng không bắt đầu

  • thread hiện hành được ưu tiên

  • có gì xảy ra vì giờ vẫn chưa bắt đầu để nâng cao sự kiện

  • chuỗi ban đầu đang chạy lại

  • kết thúc nhiệm vụ =>s_timertài liệu tham khảo bộ đếm thời gian

  • hẹn giờ đã được bắt đầu một cách an toàn: bất kỳ cuộc gọi trong tương lai để Status là hợp lệ vì s_timer là một tham chiếu hợp lệ

+0

Họ có sử dụng bộ hẹn giờ theo cách này thay vì chỉ khai báo bộ hẹn giờ như vậy: Bộ đếm thời gian mới (Trạng thái, null, 0, 2000) để ngăn chặn phương thức Trạng thái được gọi trong khi nó vẫn đang chạy? – argaz

+0

cách "bộ hẹn giờ thức dậy trên một chuỗi khác" nếu phép gán của nó (tĩnh) KHÔNG hoàn thành? –

+0

@RoyiNamir Việc gọi hàm khởi tạo của Timer sẽ khiến bộ đếm thời gian bắt đầu chạy (khi vượt qua khoảng thời gian vô hạn), phép gán biến chỉ giữ một tham chiếu khác đến đối tượng Timer và sẽ không ảnh hưởng đến hành vi của nó. – argaz

3

Nó là một cuộc đua, nhưng có nhiều hơn nó đáp ứng mắt. Chế độ thất bại rõ ràng là khi chủ đề chính mất bộ xử lý và không chạy trong một thời gian, nhiều hơn một giây. Và do đó không bao giờ được xung quanh để cập nhật biến s_timer, kaboom trong gọi lại.

Một vấn đề phức tạp hơn nhiều hiện diện trên các máy có nhiều lõi bộ xử lý. Trong đó, giá trị biến được cập nhật thực sự cần phải là hiển thị trên lõi cpu chạy mã gọi lại. Mà đọc bộ nhớ thông qua một bộ nhớ cache, bộ nhớ cache đó chịu trách nhiệm chứa nội dung cũ và vẫn có biến s_time tại null khi nó được đọc. Điều đó thường yêu cầu một rào cản bộ nhớ . Một phiên bản cấp thấp của nó có sẵn từ phương thức Thread.MemoryBarrier(). Không có mã nào trong phiên bản được đăng để đảm bảo rằng điều này xảy ra.

Nó hoạt động trong thực tế vì rào cản bộ nhớ là ẩn ngụ. Hệ điều hành không thể bắt đầu thread threadpool, bắt buộc ở đây để gọi lại, mà không cần dùng đến rào cản bộ nhớ. Tác dụng phụ của hiện tại cũng đảm bảo rằng chuỗi gọi lại sử dụng giá trị cập nhật của biến s_time. Dựa vào tác dụng phụ này không giành được bất kỳ giải thưởng nào, nhưng hoạt động trong thực tế. Nhưng cũng sẽ không hoạt động nếu cách giải quyết của Richter không được sử dụng vì rào chắn có thể được thực hiện trước khi chuyển nhượng. Và do đó chế độ thất bại tương tự trên các bộ vi xử lý với một mô hình bộ nhớ yếu, như Itanium và ARM.

+0

Thực sự tinh tế. Cảm ơn. – Pragmateek

+0

Không có "nhiều hơn một giây", 'Timer' được cho là đánh dấu lần đầu tiên ngay lập tức. – svick

+0

Không, cuộc đua nằm trên s_timer, chế độ Sleep sleep sẽ truy cập vào biến đó. –

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