web-dev-qa-db-ja.com

std :: condition_variableを待機している間にシステムクロックが変化するのをどのように処理しますか?

C++ 11でいくつかのクロスプラットフォームコードを実装しようとしています。このコードの一部は、 std :: condition_variable を使用してセマフォオブジェクトを実装します。セマフォで時限待機を行う必要がある場合は、 wait_until またはwait_forを使用します。

私が経験している問題は、POSIXベースのシステムでのcondition_variableの標準実装が 単調クロックではなくシステムクロックに依存しているように見えることです (参照: この問題に対するPOSIX仕様

つまり、システムクロックが過去のある時間に変更された場合、条件変数は予想よりもはるかに長くブロックされます。たとえば、condition_variableを1秒後にタイムアウトさせたい場合、待機中に誰かがクロックを10分戻すと、condition_variableは10分+1秒ブロックします。これがUbuntu14.04LTSシステムでの動作であることを確認しました。

少なくともある程度正確であるためには、このタイムアウトに依存する必要があります(つまり、エラーの許容範囲内で不正確になる可能性がありますが、システムクロックが変更された場合でも実行する必要があります)。私がやらなければならないことは、POSIX関数を使用し、単調クロックを使用して同じインターフェイスを実装する独自のバージョンのcondition_variableを作成することのようです。

それはたくさんの仕事のように聞こえます-そして一種の混乱。この問題を回避する他の方法はありますか?

16
jldeon

この問題の考えられる解決策を検討した後、最も理にかなっていると思われるのは、std :: condition_variableの使用を禁止することです(または、少なくとも、常にシステムクロックを使用することを明確にします)。次に、基本的に、クロックの選択を尊重する方法で、標準ライブラリのcondition_variableを自分で再実装する必要があります。

複数のプラットフォーム(Bionic、POSIX、Windows、そして最終的にはMacOS)をサポートする必要があるため、このコードのいくつかのバージョンを維持することになります。

これは厄介ですが、代替案はさらに厄介なようです。

3
jldeon

アクティブな条件変数を一元的に登録します。

現在のクロック(ick)またはその他の手段でスレッドがスピンロックしている場合でも、クロックエラーを検出するために何らかの努力をしてください。

クロックエラーを検出したら、条件変数を突く。

次に、条件変数を、クロックのずれの検出もサポートする薄いラッパーでラップします。 wait_untilを呼び出しますが、述語をクロックのずれを検出する述語に置き換え、それが発生すると待機から抜け出します。

実装が壊れたとき、あなたはあなたがしなければならないことをしなければなりません。

同じ問題が発生しました。私の同僚は、代わりに<pthread.h>の特定のC関数を使用するためのヒントをくれましたが、それは私にとって素晴らしいものでした。

例として、私は持っていました:

std::mutex m_dataAccessMutex;
std::condition_variable m_dataAvailableCondition;

その標準的な使用法で:

std::unique_lock<std::mutex> _(m_dataAccessMutex);
// ...
m_dataAvailableCondition.notify_all();
// ...
m_dataAvailableCondition.wait_for(...); 

上記は、pthread_mutex_tおよびpthread_cond_tを使用して置き換えることができます。利点は、クロックを単調に指定できることです。簡単な使用例:

#include <pthread.h>

// Declare the necessary variables
pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_condattr_t m_attr;
pthread_cond_t m_cond;

// Set clock to monotonic
pthread_condattr_init(&m_attr);
pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);
pthread_cond_init(&m_cond, &m_attr); 

// Wait on data
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_sec += timout_in_seconds; 
pthread_mutex_lock(&m_mutex);
int rc = pthread_cond_timedwait(&m_cond, &m_mutex, &ts);
if (rc != ETIMEDOUT)
    ; // do things with data
else 
    ; // error: timeout
// ...
pthread_mutex_unlock(&m_mutex); // have to do it manually to unlock
// ...

// Notify the data is ready
pthread_cond_broadcast(&m_cond);
2
vicrucann

これは最善の解決策でも、優れた解決策でもないかもしれませんが、「多くの作業」ではなく「回避策」と言ったので、次のようにします。

  • ディストリビューションの機能を使用して、システムクロックの変更を監視します(これらの機能が何であるかはよくわかりません。最悪の場合、5分ごとにcronジョブを実行して、クロックが期待値に近いことを確認できます)。
  • システムクロックの変更を検出したら、待機中/スリープ中のスレッドがあるプロセスに何かを伝えます。シグナルを使用する場合があります。またはパイプ;またはunixドメインソケット。またはいくつかの共有メモリ。
  • プロセス側では、これを確実に受信してください(つまり、シグナルハンドラーを作成するか、パイプでI/Oのブロックを実行するスレッドを作成するか、非std::condition_variable sleep-を使用して共有メモリをポーリングします)。
  • システムクロックの変更に関する通知を受け取ったら、プロセスを揺るがし、スリープ状態のスレッドを目覚めさせ、変更された時間に基づいて何をする必要があるかを再評価します。たぶんそれは以前とまったく同じです、その場合あなたは単に条件変数を再び使用するあなたのスレッドを持っています。

あまりエレガントではなく、多くのオーバーヘッドが関係していますが、これは理にかなっており、クレイジーなハックではありません。

1
einpoklum