2012-03-16 14 views
31

ブーストライブラリのCircular_bufferはスレッドセーフではありません。だから私はboost :: circular_bufferオブジェクトを下に示すようにクラスにラップしました。スレッド間の相互排除は、条件変数、ミューテックス、ロック獲得/解放を使用することによって達成される(私は思う)。この実装スレッドは安全ですか?循環バッファのスレッドセーフ実装

#include <boost/thread/condition.hpp> 
#include <boost/thread/mutex.hpp> 
#include <boost/thread/thread.hpp> 
#include <boost/circular_buffer.hpp> 

// Thread safe circular buffer 
template <typename T> 
class circ_buffer : private boost::noncopyable 
{ 
public: 
    typedef boost::mutex::scoped_lock lock; 
    circ_buffer() {} 
    circ_buffer(int n) {cb.set_capacity(n);} 
    void send (T imdata) { 
     lock lk(monitor); 
     cb.push_back(imdata); 
     buffer_not_empty.notify_one(); 
    } 
    T receive() { 
     lock lk(monitor); 
     while (cb.empty()) 
      buffer_not_empty.wait(lk); 
     T imdata = cb.front(); 
     cb.pop_front(); 
     return imdata; 
    } 
    void clear() { 
     lock lk(monitor); 
     cb.clear(); 
    } 
    int size() { 
     lock lk(monitor); 
     return cb.size(); 
    } 
    void set_capacity(int capacity) { 
     lock lk(monitor); 
     cb.set_capacity(capacity); 
    } 
private: 
    boost::condition buffer_not_empty; 
    boost::mutex monitor; 
    boost::circular_buffer<T> cb; 
}; 

編集これが今どのようなタイプ(だけでなく、cv::Matオブジェクト)のオブジェクトを受け入れ、テンプレートクラス、です。

+4

これは[CodeReview](http://codereview.stackexchange.com/)でうまくいくようです。 –

+1

私の愚かな疑問を許しますが、スレッド安全循環バッファが必要なのはどこですか?循環型バッファを使用していた時点では、このように複数のスレッドからアクセスするのは間違いでした。好奇心のためだけに、このユースケースは何ですか? – LiKao

+1

@LiKao私はネットワークカメラのフレームをMATLABに取り込むために使っています。以前の投稿を見てください。http://stackoverflow.com/questions/9472880/how-to-implement-a-circular-buffer-of-cvmat-objects-opencv 。どのようにこれにアプローチしますか? – Alexey

答えて

16

はい。
パブリックメソッドをすべて同じロックでロックすると、スレッドセーフになります。

read-write locksを使用することをお勧めします。多数の並行リーダーがある場合は、パフォーマンスが向上する可能性があります。

多くの読者がいない場合は、オーバーヘッドが追加されますが、オプションとテストを確認する価値があります。

+10

私は、読み取り/書き込みロックが循環バッファで意味をなさないとは思わない。プロデューサとコンシューマの両方がバッファを変更するので、それらはすべて実際に* writer *です。 –

+0

@DavidRodríguez-dribeas - この場合、あなたは正しいです。私は実際にスレッドの安全部分だけの設計に入っていませんでした。 –

0

buffer_not_fullの条件をまったく使用していないことを除いて、一見よく見えます。 buffer_not_emptyコードに似たコードを追加することをお勧めします。

+1

データソースがバッファに収まるよりも多くのデータを生成する場合、boost :: circular_bufferオブジェクトは最も古いデータを上書きします。これで結構です。したがって、 'buffer_not_full'条件をチェックする必要はありません。 – Alexey

5

sendで作られた無意味なコピーがあることを除いて、それはうまく見えます。新しいものは必要ありません。sendの引数を直接あなたのcbにプッシュできます。

+0

+1は、無意味で、高価で、競合が増加するコピーを避けるためです。 –

+0

@MartinJames私はsendの引数を直接押すことはできません。 "cv :: Matクラスは、参照カウントとシャローコピーを実装して、画像が別の画像に割り当てられたときに画像データがコピーされず、両方の画像が同じメモリブロックを指し示すようにします。 "参照カウントは、イメージへのすべての参照が破棄される場合にのみメモリが解放されるように保持されます。元のイメージの新しいコピーを含むイメージを作成する場合は、copyToメソッド()」 - 「OpenCV 2コンピュータビジョンアプリケーションプログラミングクックブック」(p.28) – Alexey

+1

この場合、新しいイメージはまだ必要ありません。スタックに新しいイメージを割り当てることも同様に動作します。しかし、これはあなたが望む機能ではなく、サーキュラーバッファーの共有コピーですか? –

2

お客様の実装は、bloggerで示されているものと似ています。そのブログを読んで、実装で何かが逃していないかどうかを確認する必要があります。

Matオブジェクトの作成/コピーにコストがかかる場合は、それらを連続的に作成/コピー/削除しないでください。代わりに、をリサイクルしたを何らかの種類のパイプラインアーキテクチャで継続的に取得するMatオブジェクトのプール(別名空きリスト)が必要です。このタイプのアーキテクチャについては、answerに関連する質問があります。

その回答では、ブロックスタックを使用してプールを実装することを提案しましたが、ブロックcircular_bufferを使用することもできます。スタックを提案したのは、それがよりキャッシュに優しいと思ったからですが、実際にはそれが違いを生むかどうかは実際には測定しませんでした。

+0

マットオブジェクト(画像)はそれほど大きくなく、各呼び出し中にほぼ同じサイズになります。私はちょうどスタックにMatオブジェクトを割り当てることができることに気付きました。 – Alexey

+0

私は長い間、オブジェクトプールを(ブロッキングキューに基づいて)使用してきました。実際、私はpoolObject + message-passingパターンを私のマルチスレッドアプリケーションの設計にほとんど排他的に使用しています。 no-copy、no-malloc、no-GCおよび組み込みフロー制御だけが利点ではありません。 GUIアプリケーションでは、フォームやワークスレッドの前にオブジェクトプールを作成すると、通常は明示的なプール破壊やスレッド終了の面倒を忘れることがあります。タイマーのプールレベルをダンプすると、Vole *******のような惨めなサードパーティのリークツールを使わずにリークが表示されます。 –

+0

デフォルトでは、cv :: Matのコピーコンストラクタはシャローコピーを作成します。 – Reunanen

関連する問題