web-dev-qa-db-ja.com

標準のC ++ / C ++ 11のみを使用して、「スリープ」なしでタイマーを実装できますか?

私は次のコードを持っています(手でコピー):

// Simple stop watch class basically takes "now" as the start time and 
// returns the diff when asked for.
class stop_watch {...}

// global var
std::thread timer_thread;

void start_timer(int timeout_ms)
{
    timer_thread = std::thread([timeout_ms, this](){
        stop_watch sw;
        while (sw.get_elapsed_time() < timeout_ms)
        {
            // Here is the sleep to stop from hammering a CPU
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        // Do timeout things here...
        std::cout << "timed out!" << std::endl;
    })
}

私が書いているクラスの詳細に行き詰まりたくはなかったので、これは非常に簡略化されたバージョンです。完全なクラスは関数コールバックを呼び出し、タイマーなどをキャンセルする変数を持っています...

「睡眠」の部分に集中したかっただけです。睡眠なしでこのようなものを実装できますか、それを行うより良い方法はありますか? -または、睡眠は完全に良いですか? -睡眠は一般的にデザインの悪さの兆候であるという意見でした(私はいくつかの場所を読んだことがあります)...しかし、タイマーなしでタイマーを実装する方法は考えられません:(

追加の注意:タイマーには、いつでも停止/起動できる要件が必要です。明確にするためにそれを追加するだけで、それはどのような解決策を採用するかに影響を与えるように見えるからです。元のコード(このスニペットではありません)では、ループから抜け出すことができるアトミックブールフラグを使用しました。

32
code_fodder

C++ 11はstd::condition_variable。タイマーでは、条件が満たされるまで待つことができます。

// Somewhere else, e.g. in a header:
std::mutex mutex;
bool condition_to_be_met{false};
std::condition_variable cv;

// In your timer:
// ...
std::unique_lock<std::mutex> lock{mutex};
if(!cv.wait_for(lock, std::chrono::milliseconds{timeout_ms}, [this]{return condition_to_be_met;}))
std::cout << "timed out!" << std::endl;

詳細については、こちらをご覧ください: https://en.cppreference.com/w/cpp/thread/condition_variable

条件が満たされたことを通知するには、別のスレッドでこれを行います。

{
    std::lock_guard<std::mutex> lock{mutex}; // Same instance as above!
    condition_to_be_met = true;
}
cv.notify_one();
34
Sebastian

コードは「機能」しますが、タイマーとしての目的には最適ではありません。

std::this_thread::sleep_untilが存在します。これは、実装に応じて、おそらく何らかの演算を行った後にsleep_forを呼び出すだけですが、適切なタイマーを使用するmight 、精度と信頼性の点で非常に優れています。

一般的に、睡眠は最善で、最も信頼性が高く、最も正確なことではありませんが、おおよその時間を待つだけで十分な場合は、「十分」です。

いずれにせよ、あなたの例のように少量の睡眠を繰り返すことは悪い考えです。これにより、不必要にスケジュールを変更してスレッドをウェイクアップする際に大量のCPUが消費され、一部のシステム(特にWindows 10はそれほどその点ではそれほど悪くはありませんが)では、かなりの量のジッターと不確実性が追加される可能性があります。異なるWindowsバージョンは、スケジューラの粒度differentlyに丸められるため、一般に過度に正確ではないことに加えて、一貫した動作もありません。丸めは、1つの大きな待機をほとんど「気にする」が、一連の小さな待機にとっては深刻な問題である。

タイマーを時期尚早に中止する機能が必要でない限り(ただし、その場合、それを実装するより良い方法もあります!)、スリープする必要がありますexactly once、これ以上、全期間。正確を期すために、一部のシステム(特にPOSIX)がスリープ状態になる可能性があるため、期待どおりの時間が得られたことを確認する必要があります。

必要に応じて寝坊は別の問題ですright。なぜなら、もしあなたがそのケースを正確にチェックして検出したとしても、それが起こったらあなたはそれに対してできることは何もないからです(時間はすでに過ぎていて、戻ってくる)。しかし、悲しいかな、それは睡眠の根本的な弱点に過ぎず、あなたにできることはあまりありません。幸いなことに、ほとんどの場合、ほとんどの人はこの問題を回避できます。

6
Damon

could busy-waitは、ループで時間をチェックし、待機時間に達するまで待機します。それは明らかに恐ろしいことなので、やらないでください。 10msのスリープは多少良くなりますが、デザインは間違いなく貧弱です。 ( @ Damonの答えには良い情報があります 。)


名前にsleepを含む関数を使用しても、それがプログラムで適切に実行できる場合は問題ありません。

sleepを避けるための提案は、おそらく短時間の睡眠という一般的なパターンに反して、何かすることがあるかどうかを確認してから、再び眠ることをお勧めします。代わりに、タイムアウトなし、または非常に長いタイムアウトでイベントを待機することをブロックします。 (たとえば、GUIはブロッキング関数を呼び出してキー押下/クリックを待機する必要があります。自動保存するときや、次に来るものが来るとタイムアウトになるようにタイムアウトを設定します。通常、別のスレッドは必要ありません- justスリープしますが、現在の時間にチェックを挿入するのが賢明な場所がない場合はそうするかもしれません。)

最終的に何かをする時間になったときにOSを起動させると、muchの方が良い。これにより、コンテキストの切り替えとキャッシュの汚染が回避され、短いスリープでスピンしているときに他のプログラムの速度が低下したり、電力が浪費されたりしません。

しばらくの間何もすることがないことがわかっている場合は、1回の睡眠でその時間だけ睡眠をとります。私の知る限り、複数の短いスリープは、Windows、Linux、またはOS XなどのメインストリームOSでのウェイクアップ時間の精度を改善しません。コードが頻繁にウェイクアップしている場合は、命令キャッシュを回避できますが、遅延は、おそらくリアルタイムOSと、より洗練されたタイミングのアプローチが必要な本当の問題です。

どちらかといえば、長時間スリープしているスレッドは、要求されたときに正確に起動する可能性が高く、最近実行されたスレッドが10ミリ秒だけスリープした場合、スケジューラタイムスライスの問題が発生する可能性があります。 Linuxでは、しばらくスリープ状態になっているスレッドは、起動すると優先順位が上がります。


1秒間ブロックする名前にsleepのない関数を使用することは、sleepまたは_this_thread::sleep_for_を使用することよりも優れています。

(どうやら別のスレッドが目を覚ますことができるようにする必要があります。この要件は問題に埋もれていますが、はい、条件変数はそれを行うための良い移植可能な方法です。)


純粋なISO C++ 11を使用する場合、 _std::this_thread::sleep_for_ または _std::this_thread::sleep_until_ が最善の策です。これらは、標準ヘッダー_<thread>_で定義されています。

sleep(3) は、ISO C++ 11の一部ではなく、POSIX関数(nanosleepなど)です。それがあなたにとって問題でないなら、それが適切であるなら、それを自由に使ってください。


ポータブルで高精度なOSアシストの間隔スリープのために、C++ 11が導入されました
std::this_thread::sleep_for(const std::chrono::duration<Rep, Period> &sleep_duration)(cppreferenceページには、それを使用するコード例があります。)

少なくとも指定されたsleep_durationの現在のスレッドの実行をブロックします。

この関数は、スケジューリングまたはリソース競合の遅延により、sleep_durationより長くブロックする場合があります。

規格では、持続時間の測定に安定したクロックを使用することを推奨しています。実装がシステムクロックを代わりに使用する場合、待機時間もクロック調整の影響を受ける可能性があります。


スリープするにはntilクロックが指定された時間に到達します(システム時間の変更/修正を考慮している可能性があります):

std::this_thread::sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)

指定された_sleep_time_に達するまで、現在のスレッドの実行をブロックします。

_sleep_time_に結び付けられたクロックが使用されます。つまり、クロックの調整が考慮されます。したがって、ブロックの期間は、調整の方向に応じて、呼び出し時のsleep_time - Clock::now()よりも短い場合も長い場合もあります。この関数は、スケジューリングまたはリソース競合の遅延により_sleep_time_に到達するまでよりも長くブロックすることもあります。


_sleep_for_は、システムクロックの変更による影響を受けないため、リアルタイムでスリープすることに注意してください。

ただし、_sleep_until_は、_steady_clock_以外のクロックで使用すると、システムクロックが調整(NTPまたは手動設定)によって調整された場合でも、システムクロックが所定の時間に達したときに起動できるようになっています。


睡眠覚悟:遅い/早起き

もちろん、長すぎるスリープに関する注意点は、sleepnanosleep、またはその他のOS固有のスリープまたはタイムアウト(@Sebastianの答えの条件変数アプローチを含む)にも当てはまります。やむを得ない。ただし、リアルタイムOSでは、その余分な遅延の上限を設定できます。

あなたはすでに10msのスリープでこのようなことをしています:

常にsleepまたは他の関数が遅れて起動したと仮定し、それが重要な場合は推測航法を使用する代わりに現在の時刻を確認します。

ca n't繰り返されるsleepから信頼できるクロックを構築します。例えば1秒間スリープし、デクリメントしてカウンターを表示し、さらに1秒間スリープするカウントダウンタイマーを作成しないでください。それが単なるおもちゃであり、正確さをあまり気にしない限りは。

POSIX sleep(3)などの一部のスリープ関数も、シグナルの早い段階で起動できます。早すぎる覚醒が正確性の問題である場合は、時間を確認し、必要に応じて計算された間隔でスリープ状態に戻ります。 (または_sleep_until_を使用)

3
Peter Cordes