2017-06-15 16 views
1

C++ 11でプロデューサコンシューマの問題を解決しようとしています。 リソースを保持するオブジェクトがあり、複数のスレッドでこれらのリソースを追加または使用することができます。 私の問題は、私が を実装しようとしたときに、そのオブジェクトに対して「利用可能なときに消費する」メソッドです。 挿入/削除操作が複雑ではないと仮定してください。C++でプロデューサコンシューマを正しく解決する方法11

ロジックの説明が少しあります。ほぼ同時にaddResource、のgetResourceを呼び出し、
- 2つのスレッド、T1、T2:

struct ResourceManager{ 
    std::mutex mux; 
    std::unique_lock lock{mux}; 
    std::condition_variable bell; 

    void addResource(/*some Resource*/){ 
    lock.lock(); 
    //add resource 
    lock.unlock(); 
    bell.notify_one(); //notifies waiting consumer threads to consume 
    } 

    T getResource(){ 
    while(true){ 
     lock.lock(); 
     if(/*resource is available*/){ 
     //remove resource from the object 
     lock.unlock(); 
     return resource; 
     }else{ 
     //new unique lock mutex object wmux creation 
     lock.unlock(); //problem line 
     bell.wait(wmux); //waits until addResource rings the bell 
     continue; 
     } 
    } 
    } 

}; 

次のシナリオを仮定する。

-T2はmutexをロックし、使用可能なリソースがもうないことを確認します。
したがって、新しいリソースが利用可能になるまでブロックする必要があります。
これでミューテックスのロックが解除され、ベル待機を設定します。

-T1がより速くマッチします。ミューテックスがロックされていない場合、
はすぐにリソースを追加し、T2は待機中のベルを設定する前に、
T1は既にベルを鳴らし、誰にも通知しません。

-T2は、ベルが鳴るのを無期限に待機しますが、リソースは追加されません。

私は、mutexをロックしているスレッドがロックを解除する唯一のものであるかもしれないと仮定しています。したがって、mutexのロックを解除する前にbell.waitを呼び出そうとすると、 ミューテックスをロック解除することはできません。

可能であれば、時間待機型または複数回のチェックソリューションを使用しません。
C++ 11でこの問題をどのように解決できますか?

+0

あなたの問題には関係ないかもしれませんが、mutexのロック/ロック解除には 'std :: unique_lock'のようなロックガードを使用してください。 –

+0

ロックはunique_lockです –

+0

'wmux'とは何ですか?競合状態を避けるには、 'lock'をロックし、' bell.wait(lock) 'を実行する必要があります。 – erenon

答えて

1
lock.unlock(); //problem line 
    bell.wait(wmux); //waits until addResource rings the bell 

はい、これは問題のラインです。

が正しく設計と条件変数を使用するには、は、それに関連する条件変数にINGのwait()前にmutexをロック解除しないありません。条件変数に設定すると、待ち時間中にそれを原子的にロック解除し、スレッドがnotify() -edになるとmutexを再取得します。 unlock-and-wait、およびwake-up-after-not-notified-and-lockは両方ともアトミック操作です。

すべてnotify()は、mutexがロックされている間に発行する必要があります。 wait()もすべて、ミューテックスが完全にロックされている間に実行されます。私が言及したようにnotify()がアトミックであるとすれば、mutexによって保護されているリソースの管理や、mutexで保護されている状態変数を介したスレッド通知など、すべてのmutex関連操作がアトミックで完全にシーケンスされます。

ミューテックス保護を使用せずに条件変数を通知するように実装できるデザインパターンがあります。しかし、正しく実装してもスレッドセーフなセマンティクスを実現するのはずっと難しくなります。ミューテックスによって保護されているすべての条件変数操作を持つと、ミューテックスが保護する他のすべてに加えて、実装するのがはるかに簡単です。

+0

お返事ありがとうございます。 したがって、正しく理解していれば、addResourceでは、mutexのロックが解除される前にbell.notify_one()を呼び出す必要があります。 さらに、getResourceでは、lock.unlock()を削除し、bell.wait(wmux)の代わりにbell.wait(lock)を使用する必要があります。 私はそれを正しく持っていますか? –

+0

これは正しいと思います。もちろん、これは実際の実装の詳細に依存します。これは、あなたの質問にコードを要約しただけです。注意[wait()の必要条件](http://en.cppreference.com/w/cpp/thread/condition_variable/wait): "ロック - タイプstd :: unique_lock のオブジェクトです。***現在のスレッドでロックされています*** "。 –

1

std::condition_variable::waitは、あなたのミューテックスにロックstd::unique_lockを渡す必要があります。 waitは、その操作の一部としてミューテックスのロックを解除し、戻す前にロックを解除します。

std::lock_guardstd::unique_lockのようなロックガードを使用する通常の方法は、それらをローカルで構築し、それらのコンストラクタにあなたのミューテックスをロックさせ、それらのデストラクタでロックを解除することです。

また、std::condition_variable::waitという述語を指定すると、元のコードに外部whileループが発生しないようにすることができます。

struct ResourceManager { 
    std::mutex mux; 
    std::condition_variable bell; 

    void addResource(T resource) 
    { 
    std::lock_guard<std::mutex> lock{mux}; 
    // Add the resource 
    bell.notify_one(); 
    } 

    T getResource() 
    { 
    std::unique_lock<std::mutex> lock{mux}; 
    bell.wait(lock, [this](){ return resourceIsAvailable(); }); 
    return // the ressource 
    } 
}; 
+0

ありがとう、ありがとうございました。あなたの答えは、他の人が既にここですでに答えていることをうまく説明しています。残念ながら彼は彼の投稿を削除した:S –

関連する問題