SpinWait vs Ngủ chờ. Cái nào để sử dụng?


76

Nó có hiệu quả không

SpinWait.SpinUntil(() => myPredicate(), 10000)

trong thời gian chờ 10000ms

hoặc là

Có hiệu quả hơn không khi sử dụng tính năng Thread.Sleepthăm dò cho cùng một điều kiện Ví dụ: một cái gì đó dọc theo các dòng của SleepWaithàm sau :

public bool SleepWait(int timeOut)
{
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start();
    while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut)
    {
       Thread.Sleep(50)
    }  
    return myPredicate()
}

Tôi lo ngại rằng tất cả hiệu suất của SpinWait có thể không phải là một kiểu sử dụng tốt nếu chúng ta đang nói về thời gian chờ trên 1 giây? Đây có phải là một giả định hợp lệ?

Bạn thích cách tiếp cận nào hơn và tại sao? Có cách tiếp cận nào khác tốt hơn không?


Cập nhật - Trở nên cụ thể hơn:

Có cách nào để biến BlockingCollection Pulse thành một chuỗi ngủ khi nó đạt đến dung lượng giới hạn không? Tôi muốn tránh tất cả những sự chờ đợi bận rộn như Marc Gravel gợi ý.

Câu trả lời:


51

Cách tiếp cận tốt nhất là có một số cơ chế để chủ động phát hiện điều trở thành sự thật (thay vì thăm dò một cách thụ động cho nó đã trở thành sự thật); đây có thể là bất kỳ loại xử lý chờ nào, hoặc có thể là Taskvới Wait, hoặc có thể là eventmà bạn có thể đăng ký để tự gỡ bỏ. Tất nhiên, nếu bạn thực hiện kiểu "đợi cho đến khi điều gì đó xảy ra", điều đó vẫn không hiệu quả bằng việc bạn chỉ cần thực hiện một chút công việc tiếp theo dưới dạng gọi lại , nghĩa là: bạn không cần sử dụng một chuỗi để đợi. TaskContinueWithcho điều này, hoặc bạn chỉ có thể thực hiện công việc eventkhi nó bị sa thải. Các eventcó lẽ là phương pháp đơn giản nhất, tùy thuộc vào ngữ cảnh.Tasktuy nhiên, đã cung cấp hầu hết mọi thứ bạn đang nói ở đây, bao gồm cả cơ chế "chờ hết thời gian" và "gọi lại".

Và vâng, quay trong 10 giây không phải là tuyệt vời. Nếu bạn muốn sử dụng một cái gì đó giống như mã hiện tại của mình và nếu bạn có lý do để mong đợi một thời gian trễ ngắn, nhưng cần cho phép một thời gian dài hơn - có thể SpinWaittrong (giả sử) 20ms và sử dụng Sleepcho phần còn lại?


Nhận xét lại; đây là cách tôi tạo cơ chế "nó đã đầy đủ":

private readonly object syncLock = new object();
public bool WaitUntilFull(int timeout) {
    if(CollectionIsFull) return true; // I'm assuming we can call this safely
    lock(syncLock) {
        if(CollectionIsFull) return true;
        return Monitor.Wait(syncLock, timeout);
    }
}

với, trong mã "đưa lại vào bộ sưu tập":

if(CollectionIsFull) {
    lock(syncLock) {
        if(CollectionIsFull) { // double-check with the lock
            Monitor.PulseAll(syncLock);
        }
    }
}

2
Cảm ơn, tôi thích câu trả lời của bạn, điều này thực sự sẽ tốt. Giả sử bạn đang theo dõi việc sử dụng một số tài nguyên thông qua BlockingCollection. Chúng được sử dụng (và bị loại bỏ khỏi bộ sưu tập) và trở lại bộ sưu tập khi chúng có sẵn để sử dụng lại. Việc tắt chỉ có thể xảy ra khi tất cả những thứ này đã trở lại bộ sưu tập. Bạn sẽ làm thế nào để báo hiệu rằng việc tắt máy có thể tiếp tục (tức là bộ sưu tập đã đầy trở lại) ngoài việc chờ đợi bận rộn?
Anastasiosyal

@Anastasiosyal Tôi sẽ đóng gói bộ sưu tập và yêu cầu trình bao bọc làm điều gì đó khi đầy; thực sự, tôi có thể sẽ sử dụng một Monitorcho điều này (sẽ thêm ví dụ)
Marc Gravell

Trong trình hủy của lớp 'ObjectPool' đi xuống từ BlockingQueue, hãy bật từng đối tượng từ BlockingCollection và loại bỏ chúng, (vì đây là C #, hãy đặt tham chiếu được truy xuất thành nil), cho đến khi số lượng đối tượng xuất hiện bằng với độ sâu của hồ bơi. Tất cả các đối tượng được gộp sau đó đã được xử lý, vì vậy bạn có thể loại bỏ hàng đợi và quay lại từ dtor để tiếp tục trình tự tắt. Không cần bỏ phiếu / bận chờ đợi! Bạn có thể muốn đặt một số thời gian chờ để cuối cùng một đối tượng bị rò rỉ sẽ tạo ra một ngoại lệ, (hoặc một cái gì đó).
Martin James

@MartinJames - Đó là một nhận xét hợp lệ cho hầu hết các trường hợp. Trong trường hợp này chủ sở hữu của bộ sưu tập không bị phá hủy nhưng đã trở lại một hồ bơi (nghĩ về một shutdown phiên và phiên được trả lại cho một hồ bơi)
Anastasiosyal

2
@Anastasiosyal xin lỗi của tôi - nó là Monitor.Wait- nhưng không: nó sẽ không bế tắc; đó là kỹ thuật thông thường và được sử dụng phổ biến để sử dụng màn hình làm tín hiệu. Waitnhả khóa trong khoảng thời gian, sau đó mua lại khóa trước khi trả về true hoặc false.
Marc Gravell

130

Trong .NET 4 SpinWaitthực hiện quay nhiều CPU trong 10 lần lặp lại trước khi cho kết quả. Nhưng nó không trở lại người gọi ngay sau mỗi chu kỳ đó; thay vào đó, nó gọi Thread.SpinWaitquay qua CLR (về cơ bản là HĐH) trong một khoảng thời gian nhất định. Khoảng thời gian này ban đầu là vài chục nano giây nhưng sẽ tăng gấp đôi với mỗi lần lặp cho đến khi hoàn tất 10 lần lặp. Điều này cho phép sự rõ ràng / khả năng dự đoán trong tổng thời gian dành cho giai đoạn quay (sử dụng nhiều CPU), mà hệ thống có thể điều chỉnh theo điều kiện (số lõi, v.v.). Nếu SpinWaitvẫn ở trong giai đoạn tạo spin quá lâu, nó sẽ định kỳ ngủ để cho phép các chuỗi khác tiếp tục (xem blog của J. Albahari để biết thêm thông tin). Quá trình này được đảm bảo giữ cho một lõi bận rộn ...

Vì vậy, hãy SpinWaitgiới hạn quay đòi hỏi nhiều CPU ở một số lần lặp lại nhất định, sau đó nó tạo ra lát thời gian trên mỗi vòng quay (bằng cách thực sự gọi Thread.YieldThread.Sleep), giảm mức tiêu thụ tài nguyên của nó. Nó cũng sẽ phát hiện xem người dùng có đang chạy một máy lõi đơn hay không và năng suất trên mọi chu kỳ nếu đúng như vậy.

Với Thread.Sleepchủ đề bị chặn. Nhưng quá trình này sẽ không tốn kém như trên về mặt CPU.


Điều này được sao chép từ trang web của Albahari. Nó nên được tham khảo!
georgiosd

1
Nó không được sao chép nguyên văn, nhưng bị ảnh hưởng bởi trang web của anh ấy, do đó tại sao tôi đã tham khảo blog của anh ấy.
MoonKnight

1
Cảm ơn vì lời giải thích, nó thực sự nhiều thông tin.
T Kambi
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.