0

私はブーストライブラリとC++で遊んでいます。プロデューサー、コンシューマー、およびスタックを含むマルチスレッドプログラムを作成したいと思います。プロキューダーがスタックを埋め、コンシューマーがスタックからアイテム(int)を削除します。すべてが機能します(ポップ、プッシュ、ミューテックス)しかし、ポップ/プッシュウィンシンをスレッドと呼ぶと、効果がありません

私はこの簡単なコードを作りました:

#include "stdafx.h"
#include <stack>
#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/date_time.hpp> 
#include <boost/signals2/mutex.hpp>
#include <ctime>

using namespace std;

/ *
* this class reprents a stack which is proteced by mutex
* Pop and push are executed by one thread each time.
*/
class ProtectedStack{
private : 
stack<int> m_Stack;
boost::signals2::mutex m;

public : 
ProtectedStack(){
}
ProtectedStack(const ProtectedStack & p){

}
void push(int x){
    m.lock();
    m_Stack.push(x);
    m.unlock();
}

void pop(){
    m.lock();
    //return m_Stack.top();
    if(!m_Stack.empty())
        m_Stack.pop();
    m.unlock(); 
}
int size(){
    return m_Stack.size();
}
bool isEmpty(){
    return m_Stack.empty();
}
int top(){
    return m_Stack.top();
}
};

/*
*The producer is the class that fills the stack. It encapsulate the thread object 
*/

class Producer{
public:
Producer(int number ){
    //create thread here but don't start here
m_Number=number;


}
void fillStack (ProtectedStack& s ) {
    int object = 3; //random value
    s.push(object);
    //cout<<"push object\n";
}

void produce (ProtectedStack & s){
    //call fill within a thread 
    m_Thread = boost::thread(&Producer::fillStack,this, s);  
}

 private :
int m_Number;
boost::thread m_Thread;

};


/* The consumer will consume the products produced by the producer */ 

class Consumer {
private : 
int m_Number;
boost::thread m_Thread;
public:
Consumer(int n){
    m_Number = n;
}

void remove(ProtectedStack &s ) {

     if(s.isEmpty()){ // if the stack is empty sleep and wait for the producer      to fill the stack
        //cout<<"stack is empty\n";
        boost::posix_time::seconds workTime(1); 
        boost::this_thread::sleep(workTime);
     }
     else{
        s.pop(); //pop it
        //cout<<"pop object\n";

     }

}

void consume (ProtectedStack & s){
    //call remove within a thread 
    m_Thread = boost::thread(&Consumer::remove, this, s);  
}

};


int main(int argc, char* argv[])  
{  



ProtectedStack s;


    Producer p(0);
    p.produce(s);

    Producer p2(1);
    p2.produce(s);

    cout<<"size after production "<<s.size()<<endl;
    Consumer c(0);
    c.consume(s);
    Consumer c2(1);
    c2.consume(s);
    cout<<"size after consumption  "<<s.size()<<endl;

getchar();
return 0;  
}  

VC ++ 2010 / win7で実行した後、次のようになりました:0 0

メインからfillStack関数を呼び出すと効果が得られるのに、スレッドから呼び出すと何も起こらない理由を理解するのを手伝ってもらえますか?ありがとうございました

4

3 に答える 3

5

あなたのサンプルコードには、他の人が指摘しているように、いくつかの同期の問題があります。

  • ProtectedStackの一部のメンバーへの呼び出しでロックがありません。
  • メインスレッドは、ワーカースレッドの参加を許可せずに終了する可能性があります。
  • プロデューサーとコンシューマーは、期待どおりにループしません。プロデューサーは常に(可能な場合は)プロデュースする必要があり、コンシューマーは新しい要素がスタックにプッシュされるときに消費し続ける必要があります。
  • メインスレッドでのcoutは、プロデューサーまたはコンシューマーがまだ作業する機会が得られる前に実行される可能性があります。

プロデューサーとコンシューマー間の同期に条件変数を使用することを検討することをお勧めします。ここでプロデューサー/コンシューマーの例を見てください:http://en.cppreference.com/w/cpp/thread/condition_variable これは、C ++ 11の時点で標準ライブラリのかなり新しい機能であり、VS2012の時点でサポートされています。VS2012より前は、ブーストするか、Win32呼び出しを使用する必要がありました。

条件変数を使用してプロデューサー/コンシューマーの問題に取り組むことは、共有データをロックするためにミューテックスの使用をほぼ強制し、消費者に何かが消費される準備ができていることを知らせるシグナリングメカニズムを提供するため、優れています。 (これは常に、コンシューマーの応答性とキューをポーリングするCPU使用率の間のトレードオフです)。また、それ自体がアトミックであるため、ここで説明されているように、スレッドが消費するものがあるというシグナルを見逃す可能性を防ぎます:https: //en.wikipedia.org/wiki/Sleeping_barber_problem

条件変数がこれをどのように処理するかを簡単に説明します...

  • プロデューサーは、ミューテックスを所有せずに、スレッド上ですべての時間のかかるアクティビティを実行します。
  • プロデューサーはミューテックスをロックし、生成したアイテムをグローバルデータ構造(おそらく何らかのキュー)に追加し、ミューテックスを解放し、単一のコンシューマーにこの順序で移動するように通知します。
  • 条件変数を待機しているコンシューマーは、ミューテックスを自動的に再取得し、キューからアイテムを削除して、そのアイテムに対して何らかの処理を行います。この間、プロデューサーはすでに新しいアイテムの作成に取り組んでいますが、アイテムをキューに入れる前に、コンシューマーが完了するまで待機する必要があります。

これは、コードに次の影響を及ぼします。

  • ProtectedStackはもう必要ありません。通常のスタック/キューのデータ構造で十分です。
  • 十分に新しいコンパイラを使用している場合は、ブーストの必要はありません。ビルドの依存関係を削除することは常に良いことです。

スレッド化はあなたにとってかなり新しいと感じます。あなたの心を包み込むのは非常に難しいので、他の人が同期の問題をどのように解決したかを調べるためのアドバイスしか提供できません。複数のスレッドと共有データがある環境で何が起こっているかについての混乱は、通常、将来のデッドロックなどの問題につながります。

于 2012-10-23T19:30:16.000 に答える
2

コードの主な問題は、スレッドが同期されていないことです。デフォルトでは、スレッドの実行は順序付けられておらず、順序付けもされていないため、プロデューサースレッドがデータを生成する前に、コンシューマースレッドを実際に終了できます(特定の場合は終了できます)。

プロデューサーが作業を終了した後にコンシューマーが実行されるようにするにはthread::join()、プロデューサースレッドで関数を使用する必要があります。これにより、プロデューサーが終了するまでメインスレッドの実行が停止します。

// Start producers
...

p.m_Thread.join();  // Wait p to complete
p2.m_Thread.join(); // Wait p2 to complete

// Start consumers
...

これでうまくいきますが、おそらくこれは一般的な生産者/消費者のユースケースには適していません。

より便利なケースを実現するには、消費関数を修正する必要があります。コンシューマー関数は実際には生成されたデータを待機しません。スタックが空の場合は終了し、データがまだ生成されていない場合はデータを消費しません。

これは次のようになります。

void remove(ProtectedStack &s)
{
   // Place your actual exit condition here,
   // e.g. count of consumed elements or some event
   // raised by producers meaning no more data available etc.
   // For testing/educational purpose it can be just while(true)
   while(!_some_exit_condition_)
   {
      if(s.isEmpty())
      {
          // Second sleeping is too big, use milliseconds instead
          boost::posix_time::milliseconds workTime(1); 
          boost::this_thread::sleep(workTime);               
      }               
      else
      {
         s.pop();
      }
   }
} 

もう1つの問題は、threadコンストラクターの使用法が間違っていることです。

m_Thread = boost::thread(&Producer::fillStack, this, s);  

Boost.Threadドキュメントからの引用:

引数付きのスレッドコンストラクタ

template <class F,class A1,class A2,...> thread(F f,A1 a1,A2 a2,...);

前提条件: Fそして、それぞれAnがコピー可能または移動可能でなければなりません。

効果:まるでthread(boost::bind(f,a1,a2,...))。その結果、fと各anは、新しいスレッドによるアクセスのために内部ストレージにコピーされます

これは、各スレッドが独自のコピーを受け取り、sすべての変更がsローカルスレッドコピーに適用されるのではなく、適用されることを意味します。オブジェクトを値で関数の引数に渡す場合も同じです。s代わりに、参照によってオブジェクトを渡す必要があります-を使用してboost::ref

void produce(ProtectedStack& s)
{
   m_Thread = boost::thread(&Producer::fillStack, this, boost::ref(s));
}

void consume(ProtectedStack& s)
{
   m_Thread = boost::thread(&Consumer::remove, this, boost::ref(s));
}  

もう1つの問題は、ミューテックスの使用法に関するものです。それは可能な限り最高ではありません。

  1. Signals2ライブラリのミューテックスを使用するのはなぜですか?Boost.Threadから使用boost::mutexして、Signals2ライブラリへの不要な依存関係を削除してください。

  2. boost::lock_guard直接lock/unlock呼び出しの代わりにRAIIラッパーを使用します。

  3. 他の人が言ったように、あなたはのすべてのメンバーをロックで保護しなければなりませんProtectedStack

サンプル:

boost::mutex m;

void push(int x)
{ 
   boost::lock_guard<boost::mutex> lock(m);
   m_Stack.push(x);
} 

void pop()
{
   boost::lock_guard<boost::mutex> lock(m);
   if(!m_Stack.empty()) m_Stack.pop();
}              

int size()
{
   boost::lock_guard<boost::mutex> lock(m);
   return m_Stack.size();
}

bool isEmpty()
{
   boost::lock_guard<boost::mutex> lock(m);
   return m_Stack.empty();
}

int top()
{
   boost::lock_guard<boost::mutex> lock(m);
   return m_Stack.top();
}
于 2012-10-23T15:49:27.670 に答える
1

消費しようとする前に、生成スレッドが実行されたことを確認していません。また、サイズ/空/トップをロックしていません...コンテナが更新されている場合、これは安全ではありません。

于 2012-10-23T13:48:25.953 に答える