2017-10-07 3 views
0

UIスレッド&ワーカースレッド間のデータトランザクションにC++11std::condition_variableを使用しようとしています。C++:UIスレッドとワーカーstd :: threadの間でstd :: condition_variableを使用する方法

状況:
m_calculated_valueは複雑なロジックの後に算出した値です。これは、UIスレッドからのイベントのトリガーで必要です。 UIスレッドは、を呼び出して、という値を取得します。この値は、ワーカースレッド関数MyClass::ThreadFunctionToCalculateValueによって計算される必要があります。

コード:

std::mutex    m_mutex; 
std::condition_variable m_my_condition_variable; 
bool     m_value_ready; 
unsigned int   m_calculated_value; 


// Gets called from UI thread 
unsigned int MyClass::GetCalculatedValue() { 

    std::unique_lock<std::mutex> lock(m_mutex); 
    m_value_ready = false; 

    m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this)); 

    return m_calculated_value; 
} 


bool MyClass::IsValueReady() { 

    return m_value_ready; 
} 

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 

    std::unique_lock<std::mutex> lock(m_mutex); 

    m_calculated_value = ComplexLogicToCalculateValue(); 
    m_value_ready = true; 

    m_my_condition_variable.notify_one(); 
} 

問題:
しかし、問題はm_my_condition_variable.waitは決して戻らないということです。

質問:
は、私がここで間違って何をしているのですか?

ワーカースレッドからの条件変数信号でUIスレッドが待機する正しい方法はありますか? condition_variableがワーカースレッド関数のエラーのために決してトリガしない状況からどうやって抜け出すのですか?何とかここでタイムアウトを使用できる方法はありますか?

は、それがどのように動作するかを理解しようとすると:
ループがcondition_var.wait周りブール変数の状態を確認しながら、私は、彼らがを使用する多くの例で参照してください。変数の周りのループのポイントとは何ですか? 他のスレッドからnotify_oneを呼び出すと、m_my_condition_variablewaitから戻ると思いますか?

+0

あなたが参照している例は、述語と待機() ''のオーバーロードを使用していませんでした。したがって、彼らは基本的にそれらを再実装しました。それにもかかわらず、あなたのユースケースでは、条件変数ではなく将来を使うべきです。 – Ext3h

+0

'MyClass :: GetCalculatedValue'が返ってきたら、必要な値を準備するために' m_calculated_value'が必要です。これは、データが準備完了であることを伝えるために、スレッド間のシグナリングの典型的なケースのように思えます。 @ Wxt3hあなたもそうだと思いませんか?将来はどのようにここで助けますか?もう少し詳しく教えていただけますか? –

+0

私は間違っていますが、あなたの症状を引き起こすものは何もありません。あなたは複数の質問をし、[mcve]を提供しません。状態変数をどのようにusrにするかをカバーするSOのQ&Aがあります。私はそれらを読んでアドバイスし、彼らの言うことをお勧めします。 – Yakk

答えて

0

ここで、この例を見てみましょう:以下の例のコード内のコメントで述べた問題のコードに

http://en.cppreference.com/w/cpp/thread/condition_variable

変更。 cppreference.comの例で使用されているのと同じ「ハンドシェイク」を使用して、新しい値を計算するのが安全なときに同期することを検討するとよいでしょう(UIスレッドには待機/通知があり、ワーカースレッドには通知/待機があります)。

条件変数が待機する前に、ロックをロックする必要があります。待機はロック解除され、通知を待ってから、ロックし、述語関数で準備ができているかどうかをチェックし、準備ができていない場合(偽の起床)、サイクルを繰り返します。

notify_oneの前に、ロックを解除する必要があります。それ以外の場合は、ウォークアップが発生しますが、まだロックされているためロックが失敗します。

std::mutex    m_mutex; 
std::condition_variable m_my_condition_variable; 
bool     m_value_ready = false; // init to false 
unsigned int   m_calculated_value; 


// Gets called from UI thread 
unsigned int MyClass::GetCalculatedValue() { 
    std::unique_lock<std::mutex> lock(m_mutex); 
    m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this)); 
    m_value_ready = false; // don't change until after wait 
    return m_calculated_value; 
} // auto unlock after leaving function scope 

bool MyClass::IsValueReady() { 

    return m_value_ready; 
} 

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 
    std::unique_lock<std::mutex> lock(m_mutex); 
    m_calculated_value = ComplexLogicToCalculateValue(); 
    m_value_ready = true; 
    lock.unlock();   // unlock before notify 
    m_my_condition_variable.notify_one(); 
} 

または代替:

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 

    { // auto unlock after leaving block scope 
     std::lock_guard<std::mutex> lock(m_mutex); 
     m_calculated_value = ComplexLogicToCalculateValue(); 
     m_value_ready = true; 
    } // unlock occurs here 
    m_my_condition_variable.notify_one(); 
} 
3

発生する可能性が最も高いもの: ワーカースレッドは、計算が完了するまでミューテックスを所有し保持します。メインスレッドは、ロックを獲得できるまで待たなければなりません。作業者はの前にCV を通知し、(デストラクタ内の)ロックを解除します。この時点までに、条件変数で待機したい他のスレッドは、通知スレッドがまだ占有しているロックを取得できませんでした。したがって、他のスレッドは、通知イベントが発生した後にロックを取得することができただけで通知を受け取った時点で条件変数を待つ機会を得られず、無限に待機しました。

解決策は、MyClass :: ThreadFunctionToCalculateValue()のロック取得を削除することです。それは必ずしも必要ではありません。少なくとも、必要はありません。

しかし、とにかく、なぜホイールを再発明したいのですか?このような問題のために、std::futureが作成されています。ここでは

auto future = std::async(std::launch::async, ComplexLogicToCalculateValue); 
bool is_ready = future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; 
auto result = future.get(); 

、あなたは簡単にタイムアウトを定義することができ、あなたがcondition_variablesと同様に心配する必要はありません。

私は、notify_oneが他のスレッドから呼び出されたときに、m_my_condition_variableが待機状態から復帰することを期待していますか?

No(排他的ではない)。偽の起床がまだ発生する可能性があります。

+0

ここでcondition_variableは使用しないでください。助けてくれてありがとう –

+0

私は偽の目覚ましを扱うことができると思いますか? 'condition_variable'が少なくともこのシナリオでうまくいくならば、私は偽の後退を扱うことができます。 –

+0

もちろん可能です。しかし、手元にあるよりクリーンなソリューションが存在するのであれば、なぜ迷惑でしょうか? – Jodocus

関連する問題