簡體   English   中英

condition_variable::wait_for() 如何處理虛假喚醒?

[英]How does condition_variable::wait_for() deal with spurious wakeups?

各種平台都允許虛假喚醒 為了解決這個問題,我們寫了下面的循環機制:

while(ContinueWaiting())
  cv.wait(lock);   // cv is a `std::conditional_variable` object

對於conditional_variable::wait_until()來說,同樣的事情是可以理解的。
但看看下面的例子:

const auto duration = Returns_10_seconds();
while(!Predicate())
  cv.wait_for(lock, duration);

想象一下,虛假喚醒發生在 1 秒。 尚未達到超時。
它會再等10秒嗎? 這會導致無限循環,我確信這不應該發生。 從源代碼中,內部wait_for()調用wait_until()

我想了解, wait_for()如何處理虛假喚醒?

我想了解, wait_for()如何處理虛假的喚醒?

它沒有。

此功能通常用於以下情況:如果您虛假地醒來,您還是想做其他一些工作。 如果你沒有虛假地醒來,你想要在duration過去之前強制“虛假”醒來。 這意味着,正如您所說明的那樣,它通常不會在您顯示的循環中使用。 即超時和虛假的喚醒被同樣對待。

現在您可能想知道,謂詞版本的作用是什么,因為它意味着循環?

template <class Rep, class Period, class Predicate>
bool
wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time,
         Predicate pred);

這被指定具有與以下相同的效果:

return wait_until(lock, chrono::steady_clock::now() + rel_time, std::move(pred));

wait_until變體確實區分了虛假喚醒和超時。 它是這樣的循環:

while (!pred())
    if (wait_until(lock, abs_time) == cv_status::timeout)
        return pred();
return true;

以下是關於虛假喚醒的標准:

30.5條件變量[thread.condition]

條件變量提供用於阻塞線程的同步原語,直到某個其他線程通知滿足某些條件或者直到達到系統時間為止。

...

10注意:用戶有責任確保等待線程在錯誤喚醒時不會錯誤地認為線程已完成。

從措辭中可以清楚地看出,處理虛假喚醒的責任在於用戶。

 
 
  
  
 
  
    const auto duration = Returns_10_seconds();
    while(cv.wait_for(lock, duration) == std::cv_status::timeout);

 

 
 

\n

這絕對是一件錯誤的事情,因此沒有必要討論如何針對虛假喚醒的情況進行修復,因為即使在普通喚醒的情況下它也會被破壞,因為等待狀態在從返回之后不會被重新檢查這段等待。

const auto duration = Returns_10_seconds();
while(!Predicate())
  cv.wait_for(lock, duration);

即使在編輯之后,答案也保持不變:你無法真正處理“虛假的喚醒”,因為你無法真正說出喚醒的原因 - 由於調用condition_variable::notifyXXX它可能是完全合法的喚醒condition_variable::notifyXXX超時到期之前的condition_variable::notifyXXX

首先,請注意,您無法真正區分由condition_variable::notifyXXX調用引起的喚醒和由POSIX信號[1]引起的喚醒。 其次,即使POSIX信號不受關注,等待線程仍然必須重新檢查條件,因為條件變量發出信號的時間和等待線程從條件等待返回之間的條件可能會發生變化。

你真正需要做的是以特殊的方式對待,不要在超時前醒來,而是因超時而醒來。 這完全取決於首先出現超時的原因,即應用程序/問題域的具體情況。

[1]如果等待條件變量被信號中斷,則在執行信號處理程序后,允許線程恢復等待或返回

自己檢查一下自己的電話

cv.wait_for()不處理虛假喚醒。

您可以通過引用將布爾標志傳遞給線程來處理虛假cv.wait_until() ,並在cv.wait_until()不是超時時檢查它。

在這種情況下,當main()線程沒有設置terminate並且cv.wait_until()具有no_timeout時,這意味着沒有達到超時但是cv被通知(由系統),因此它是一個虛假的調用。

bool terminate = false;
std::unique_lock<std::mutex> lock(mutex);
const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
const std::cv_status status = cv.wait_until(lock, time_point);
if (status == std::cv_status::timeout) {
    std::cout << "timeout" << std::endl;
}
else { // no_timeout
    if (terminate) {
        std::cout << "terminate" << std::endl;
        break;
    }
    else {
        std::cout << "spurious" << std::endl;
    }
}

完整代碼

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>

class Thread {
private:
    std::condition_variable cv;
    std::mutex mutex;
    bool terminate = false;
    std::thread thread;
public:
    Thread() {
        thread = std::thread([&]() {
            while (true) {
                std::unique_lock<std::mutex> lock(mutex);
                const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
                const std::cv_status status = cv.wait_until(lock, time_point);
                if (status == std::cv_status::timeout) {
                    std::cout << "timeout" << std::endl;
                }
                else { // no_timeout
                    if (terminate) {
                        std::cout << "terminate" << std::endl;
                        break;
                    }
                    else {
                        std::cout << "spurious" << std::endl;
                    }
                }
            }
        });
    }
    virtual ~Thread() {
        {
            std::lock_guard<std::mutex> lock(mutex);
            terminate = true;
        }
        cv.notify_all();
        if (thread.joinable()) {
            thread.join();
        }
    }
};

void main() {
    {
        Thread thread;
        std::this_thread::sleep_for(std::chrono::seconds(15));
    }
    std::cin.get();
}

main()sleep_for()時的完整代碼結果

5 seconds:
terminate

15 seconds:
timeout
terminate

15 seconds in Visual Studio Debug mode:
spurious
terminate

我有同樣的擔憂,但我檢查了源代碼,似乎 wait_for 和 wait_until 都可以處理虛假喚醒,如果我錯了,誰能糾正我?

template<typename _Clock, typename _Duration, typename _Predicate>
  bool
  wait_until(unique_lock<mutex>& __lock,
     const chrono::time_point<_Clock, _Duration>& __atime,
     _Predicate __p)
  {
while (!__p())
  if (wait_until(__lock, __atime) == cv_status::timeout)
    return __p();
return true;
  }

template<typename _Rep, typename _Period>
  cv_status
  wait_for(unique_lock<mutex>& __lock,
       const chrono::duration<_Rep, _Period>& __rtime)
  {
using __dur = typename __steady_clock_t::duration;
auto __reltime = chrono::duration_cast<__dur>(__rtime);
if (__reltime < __rtime)
  ++__reltime;
return wait_until(__lock, __steady_clock_t::now() + __reltime);
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM