3

私が取り組んでいるプロジェクトのために、生産者/消費者モデルのマルチスレッド プログラムを C++ で実装しようとしています。基本的な考え方は、メイン スレッドが 2 番目のスレッドを作成してシリアル ポートで新しいデータを監視し、データを処理して、メイン スレッドによって定期的にポーリングされるバッファに結果を格納するというものです。私はこれまでマルチスレッドプログラムを書いたことがありません。たくさんのチュートリアルを読んできましたが、それらはすべて C で書かれています。基本的な概念は理解できたと思いますが、それを c++ 化しようとしています。バッファーについては、ミューテックス保護が組み込まれたデータ クラスを作成したいと考えています。これが私が思いついたものです。

1) 私はこれについて間違った方法で行っていますか? 保護されたデータ クラスを実装するよりスマートな方法はありますか?

ProtectedBuffer::add_back()2) 2 つのスレッドが同時に呼び出そうとすると、次のコードはどうなりますか?

#include <deque>
#include "pthread.h"

template <class T>
class ProtectedBuffer {
  std::deque<T> buffer;
  pthread_mutex_t mutex;
public:
  void add_back(T data) {
    pthread_mutex_lock(&mutex);
    buffer.push_back(data);
    pthread_mutex_unlock(&mutex);
  }
  void get_front(T &data) {
    pthread_mutex_lock(&mutex);
    data = buffer.front();
    buffer.pop_front();
    pthread_mutex_unlock(&mutex);
  }
};

編集:すべての素晴らしい提案をありがとう。以下にそれらを実装しようとしました。また、いくつかのエラー チェックを追加したので、スレッドが何らかの方法で同じミューテックスを 2 回ロックしようとすると、正常に失敗します。おもう。

#include "pthread.h"
#include <deque>


class Lock {
    pthread_mutex_t &m;
    bool locked;
    int error;
public:
    explicit Lock(pthread_mutex_t & _m) : m(_m) {
        error = pthread_mutex_lock(&m);
        if (error == 0) {
            locked = true;
        } else {
            locked = false;
        }
    }
    ~Lock() {
        if (locked)
            pthread_mutex_unlock(&m);
    }
    bool is_locked() {
        return locked;
    }
};

class TryToLock {
    pthread_mutex_t &m;
    bool locked;
    int error;
public:
    explicit TryToLock(pthread_mutex_t & _m) : m(_m) {
        error = pthread_mutex_trylock(&m);
        if (error == 0) {
            locked = true;
        } else {
            locked = false;
        }
    }
    ~TryToLock() {
        if (locked)
            pthread_mutex_unlock(&m);
    }
    bool is_locked() {
        return locked;
    }
};

template <class T>
class ProtectedBuffer{
    pthread_mutex_t mutex;
    pthread_mutexattr_t mattr;
    std::deque<T> buffer;
    bool failbit;

    ProtectedBuffer(const ProtectedBuffer& x);
    ProtectedBuffer& operator= (const ProtectedBuffer& x);
public:
    ProtectedBuffer() {
        pthread_mutexattr_init(&mattr);
        pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
        pthread_mutex_init(&mutex, &mattr);
        failbit = false;
    }
    ~ProtectedBuffer() {
        pthread_mutex_destroy(&mutex);
        pthread_mutexattr_destroy(&mattr);
    }
    void add_back(T &data) {
        Lock lck(mutex);
        if (!lck.locked()) {
            failbit = true;
            return;
        }
        buffer.push_back(data);
        failbit = false;
    }
    void get_front(T &data) {
        Lock lck(mutex);
        if (!lck.locked()) {
            failbit = true;
            return;
        }
        if (buffer.empty()) {
            failbit = true;
            return;
        }
        data = buffer.front();
        buffer.pop_front();
        failbit = false;
    }
    void try_get_front(T &data) {
        TryToLock lck(mutex);
        if (!lck.locked()) {
            failbit = true;
            return;
        }
        if (buffer.empty()) {
            failbit = true;
            return;
        }
        data = buffer.front();
        buffer.pop_front();
        failbit = false;
    }
    void try_add_back(T &data) {
        TryToLock lck(mutex);
        if (!lck.locked()) {
            failbit = true;
            return;
        }
        buffer.push_back(data);
        failbit = false;
    }
};
4

4 に答える 4

9

いくつかのこと:

  • mutexコンストラクタで初期化し、デストラクタでpthread_mutex_init解放する必要があります。pthread_mutex_destroy

  • クラスをコピー不可かつ代入不可にする必要があります (または、コピー コンストラクターと代入演算子を正しく実装します。上記を参照してください)。

  • ロック用の SBRM ヘルパー クラスを作成する価値があります。

    class Lock
    {
        pthread_mutex_t & m;
    public:
        explicit Lock(pthread_mutex_t & _m) : m(_m) { pthread_mutex_lock(&m); }
        ~Lock() { pthread_mutex_unlock(&m); }
    };
    

    のような同期スコープを作成できるようになりました{ Lock lk(mutex); /* ... */ }

質問 2 について: 同時アクセスはミューテックスをロックすることによってシリアライズされます。競合するスレッドの 1 つがミューテックス ロックの取得時にスリープします。

于 2012-07-24T23:27:39.890 に答える
1

私はこれについて間違った方法で進んでいますか?保護されたデータ クラスを実装するよりスマートな方法はありますか?

あなたが持っている実装については、良いスタートを切ったと思います。C++ 化について質問されたので、C++11 をサポートするコンパイラがあれば、新しいスレッド サポートを使用できます。

あなたは、メイン スレッドがこのバッファをポーリングすることを望んでいたと述べましたが、それを可能にするメカニズムは見当たりませんでした。get_frontバッファーに何もない場合はエラーを提供するかget_buffer、データが利用可能になるまで呼び出し元をブロックする必要があります。

#include <deque>
#include <mutex>
#include <condition_variable>
#include <stdexcept>

template <class T>
class ProtectedBuffer {
  std::deque<T> buffer;
  std::mutex mtx;
  std::condition_variable empty_cnd;
  void get_front_i(T &data) {
    data = buffer.front();
    buffer.pop_front();
  }
public:
  void add_back(T data) {
    std::lock_guard<std::mutex> g(mtx);
    bool was_empty = buffer.empty();
    buffer.push_back(data);
    if (was_empty) empty_cnd.notify_one();
  }
  void get_front_check(T &data) {
    std::lock_guard<std::mutex> g(mtx);
    if (buffer.empty()) throw std::underflow_error("no data");
    get_front_i(data);
  }
  void get_front_block(T &data) {
    std::lock_guard<std::mutex> g(mtx);
    std::unique_lock<std::mutex> u(mtx);
    while (buffer.empty()) empty_cnd.wait(u);
    get_front_i(data);
    if (!buffer.empty()) empty_cnd.notify_one();
  }
};

バッファーに追加するデータの量を制限したい場合は、同様のfull_cnd条件変数を追加して、 add_backtrue の場合に呼び出しが待機する完全な条件を確認できます。次に、get_front_iメソッドは、バッファがいっぱいでなくなったときにシグナルを送ることができます。

2 つのスレッドが ProtectedBuffer::add_back() を同時に呼び出そうとすると、次のコードはどうなりますか?

は相互排除から保護されているためadd_back、2 つのスレッドが同時に呼び出した場合、push_back一方のスレッドは他方のスレッドが完了するまで呼び出しをブロックされます。

于 2012-07-25T00:15:35.213 に答える
0

基本はありますが、ミューテックス自体を独自のRAIIラッパーでラップすることでさらに一歩進めます。例:

#include <deque> 
#include "pthread.h" 

class ProtectedMutex
{
  pthread_mutex_t &mutex; 
public:
  ProtectedMutex(pthread_mutex_t &m)
    : mutex(m); 
  {
    pthread_mutex_lock(&mutex); 
  }
  ~ProtectedMutex()
  {
    pthread_mutex_unlock(&mutex); 
  }
};

template <class T> 
class ProtectedBuffer { 
  std::deque<T> buffer; 
  pthread_mutex_t mutex; 
public: 
  void add_back(T data) { 
    ProtectedMutex m(mutex); 
    buffer.push_back(data); 
  } 
  void get_front(T &data) { 
    ProtectedMutex m(mutex); 
    data = buffer.front(); 
    buffer.pop_front(); 
  } 
}; 
于 2012-07-24T23:32:56.620 に答える
0

「メイン スレッドによって定期的にポーリングされるバッファに結果を格納する」 - CPU の浪費とレイテンシ。

「私はこれについて間違った方法で行っていますか?」- はい。お使いのシステムでセカンダリ スレッド<> GUI スレッド通信がどのようなサポートを受けているかはわかりませんが、PostMessage() API は常に存在します。

確かに、シリアルrxデータ用のデータメンバーと、プロトコル/「データの処理」を行うメソッドを備えたBufferクラスが必要です。他にはあまり必要ありません。2 番目のスレッドで、バッファ クラスのインスタンスを作成します。ロードしてデータを処理し、GUI スレッドへのポインタを PostMessage/dispatch/BeginInvoke します。シリアル スレッドの次のコード行で、シリアル ポートからの次のデータ ロードのために、同じインスタンス ポインタ var に別のインスタンスを作成します。GUI での表示/ロギングなどの後、GUI スレッドは受け取った *Buffer を delete() する必要があります。

待ち時間なし、CPU の浪費なし、データのコピーなし、シリアル スレッドと GUI スレッドが同じバッファー インスタンスで動作する可能性なし、厄介で複雑なバッファー共有コードなし、ロックなし、手間なし。それだけでうまくいきます。

それ以外はぐちゃぐちゃになります。

編集 - (2) を忘れました - わかりません。バージポールでそれに触れないだろう..

于 2012-07-24T23:42:52.357 に答える