web-dev-qa-db-ja.com

プロダクション対応のロックフリーキューまたはC ++のハッシュ実装はありますか

私はC++のロックフリーキューをかなりグーグルで探してきました。いくつかのコードといくつかの試用版が見つかりましたが、コンパイルできるものは何もありませんでした。ロックフリーハッシュも歓迎します。

要約:これまでのところ、肯定的な答えはありません。 「プロダクション対応」ライブラリはなく、驚くべきことに、既存のライブラリはどれもSTLコンテナのAPIに準拠していません。

75
RED SOFT ADAIR

1.53の時点で、boostは、キュー、スタック、単一生産者/単一消費者キュー(リングバッファーなど)を含む ロックフリーデータ構造のセット を提供します。

39
Nova

開始点は、 単一のプロデューサーとコンシューマー または 複数のもの のいずれかのハーブサッターのDDJ記事のいずれかです。彼が提供するコード(各記事の2ページ目からインライン)は、C++ 0xスタイルのatomic <T>テンプレートタイプを使用しています。 Boostプロセス間ライブラリを使用して模倣できます。

ブーストコードはプロセス間ライブラリの奥深くに埋もれていますが、適切なヘッダーファイル(atomic.hpp)を使用して、見た目のサウンドに精通しているシステムで必要な比較とスワップ操作の実装を読み取りました。

25
Steve Gilham

Facebookの Folly は、C++ 11 <atomic>

これらは現在実稼働環境で使用されていると言ってよいので、他のプロジェクトでも安全に使用できると思います。

乾杯!

15
André Neves

はい!

I ロックフリーキューを作成しました 。 Features™があります:

  • 完全に待機なし(CASループなし)
  • 超高速 (1秒あたり1億を超えるエンキュー/デキュー操作)
  • C++ 11移動セマンティクスを使用
  • 必要に応じて成長します(ただし、必要な場合のみ)
  • 要素のロックフリーメモリ管理を行います(事前に割り当てられた連続ブロックを使用)
  • スタンドアロン(2つのヘッダーとライセンスとreadme)
  • MSVC2010 +、Intel ICC 13、およびGCC 4.7.2でコンパイルします(C++ 11に完全に準拠したコンパイラーでも動作するはずです)

GitHubで利用可能 は、単純化されたBSDライセンスの下です(気軽にフォークしてください!)。

警告:

  • 単一生産者、単一消費者アーキテクチャのみ(つまり、2つのスレッド)
  • X86(-64)で徹底的にテストされており、ARM、PowerPC、および整列されたネイティブサイズの整数とポインターのロードとストアが自然にアトミックである他のCPUで動作するはずですが、非x86 CPUでフィールドテストされていません(誰かが持っている場合テストするものを教えてください)
  • 特許が侵害されているかどうかはわかりません(ご自身の責任で使用するなど)。ゼロから設計し、実装したことに注意してください。
14
Cameron

このようなライブラリはありますが、Cにあります。

C++へのラッピングは簡単なはずです。

http://www.liblfds.org

11
user82238

boost.lockfreeは、lockfreeスタックおよびfifoクラスのc ++実装を作成する試みです。

パブリックgitリポジトリ

10
tim

与えられた答えのほとんどをチェックした後、私は述べることができるだけです:

答えは[〜#〜] no [〜#〜]です。

箱から出してすぐに使用できるようなものはありません。

10
RED SOFT ADAIR

私が知っている最も近いものは、 Windows連動単一リンクリスト です。もちろん、Windowsのみです。

6

複数のプロデューサー/単一の消費者のキュー/ FIFOがある場合は、SLISTまたは簡単なロックフリーを使用して1つのLockFreeを簡単に作成できますLIFOスタック。2番目の「プライベート」コンシューマ用のスタック(簡単にするためにSLISTとして実行することも、選択した他のスタックモデル)コンシューマはプライベートスタックからアイテムをポップします。プライベートLIFOが強調されるたびに、共有コンカレントSLISTからポップするのではなくフラッシュを実行し(SLISTチェーン全体を取得し)、フラッシュされたリストを順番にウォークして、アイテムをプライベートスタックにプッシュします。

これは、単一生産者/単一消費者および複数生産者/単一消費者に対して機能します。

ただし、複数の消費者の場合(単一のプロデューサーまたは複数のプロデューサーのいずれか)では機能しません。

また、ハッシュテーブルに関する限り、これらはキャッシュのセグメントごとにロックを持つセグメントにハッシュを分割する「ストライピング」の理想的な候補です。これは、Java同時実行ライブラリが(32ストライプを使用して)行う方法です。軽量のリーダー/ライターロックがある場合、同時読み取りのためにハッシュテーブルに同時にアクセスできます。競合するストライプで書き込みが発生している場合にのみ失速します(ハッシュテーブルの拡大を許可している場合)。

独自のロールを作成する場合、すべてのロックを互いに隣り合った配列に配置するのではなく、ロックをハッシュエントリでインターリーブするようにしてください。そうすれば、誤った共有が発生する可能性が低くなります。

5
Adisak

これに少し遅れて来るかもしれません。

解決策の欠如(質問されたとき)は、主にC++の重要な問題(C++ 0x/11より前)によるものです。C++には並行メモリモデルがない(持っている)。

現在、std :: atomicを使用して、メモリの順序付けの問題を制御し、適切な比較およびスワップ操作を行うことができます。 C++ 11とMichealのハザードポインター(IEEE TPDS 2004)を使用してMicheal&Scottのロックフリーキュー(PODC96)の実装を作成し、早期のフリーおよびABAの問題を回避しました。正常に機能していますが、迅速で汚い実装であり、実際のパフォーマンスに満足していません。コードはbitbucketで利用可能です: LockFreeExperiment

ダブルワードCASを使用してハザードポインタなしでロックフリーキューを実装することも可能です(ただし、64ビットバージョンはx86-64でcmpxchg16bを使用する場合のみ可能です)、私はそれについてのブログ投稿をしています(キューのテストされていないコードを使用) : x86/x86-64の汎用ダブルワード比較とスワップの実装 (LSEブログ)

私自身のベンチマークでは、ダブルロックキュー(Micheal&Scott 1996の論文でも)がロックフリーキューと同様に機能することを示しています(ロックされたデータ構造にパフォーマンスの問題があるほど十分な競合に達していませんが、私のベンチは軽すぎます)今)、IntelのTBBからの並行キューは、比較的少ない数(オペレーティングシステムによって異なりますが、FreeBSD 9では、これまでに発見した最低の範囲であり、この数は8スレッドです)スレッドの4 htコア、したがって8つの論理CPUを備えたi7は非常に奇妙な動作をします(私の単純なベンチマークの実行時間は数秒から数時間になります!)

STLスタイルに従うロックフリーキューに関するもう1つの制限:ロックフリーキューにイテレータを設定しても意味がありません。

4
Marwan Burelle

そして Intel Threading Building Blocks が登場しました。そして、しばらくの間、それは良かった。

PS:あなたはconcurrent_queueとconcurrent_hash_mapを探しています

3
Edouard A.

私の知る限り、そのようなものはまだ公開されていません。実装者が解決する必要のある問題の1つは、ロックなしのメモリアロケータが必要なことです。このメモリアロケータは存在しますが、現時点ではリンクを見つけることができないようです。

1
Tobias

以下は、並行ロック解放キューに関するHerb Sutterの記事 http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1 からのものです。コンパイラの並べ替えなど、いくつかの変更を加えました。このコードをコンパイルするには、GCC v4.4 +が必要です。

#include <atomic>
#include <iostream>
using namespace std;

//compile with g++ setting -std=c++0x

#define CACHE_LINE_SIZE 64

template <typename T>
struct LowLockQueue {
private:
    struct Node {
    Node( T* val ) : value(val), next(nullptr) { }
    T* value;
    atomic<Node*> next;
    char pad[CACHE_LINE_SIZE - sizeof(T*)- sizeof(atomic<Node*>)];
    };
    char pad0[CACHE_LINE_SIZE];

// for one consumer at a time
    Node* first;

    char pad1[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among consumers
    atomic<bool> consumerLock;

    char pad2[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

// for one producer at a time
    Node* last;

    char pad3[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among producers
    atomic<bool> producerLock;

    char pad4[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

public:
    LowLockQueue() {
    first = last = new Node( nullptr );
    producerLock = consumerLock = false;
    }
    ~LowLockQueue() {
    while( first != nullptr ) {      // release the list
        Node* tmp = first;
        first = tmp->next;
        delete tmp->value;       // no-op if null
        delete tmp;
    }
    }

    void Produce( const T& t ) {
    Node* tmp = new Node( new T(t) );
    asm volatile("" ::: "memory");                            // prevent compiler reordering
    while( producerLock.exchange(true) )
        { }   // acquire exclusivity
    last->next = tmp;         // publish to consumers
    last = tmp;             // swing last forward
    producerLock = false;       // release exclusivity
    }

    bool Consume( T& result ) {
    while( consumerLock.exchange(true) )
        { }    // acquire exclusivity
    Node* theFirst = first;
    Node* theNext = first-> next;
    if( theNext != nullptr ) {   // if queue is nonempty
        T* val = theNext->value;    // take it out
        asm volatile("" ::: "memory");                            // prevent compiler reordering
        theNext->value = nullptr;  // of the Node
        first = theNext;          // swing first forward
        consumerLock = false;             // release exclusivity
        result = *val;    // now copy it back
        delete val;       // clean up the value
        delete theFirst;      // and the old dummy
        return true;      // and report success
    }
    consumerLock = false;   // release exclusivity
    return false;                  // report queue was empty
    }
};

int main(int argc, char* argv[])
{
    //Instead of this Mambo Jambo one can use pthreads in Linux to test comprehensively
LowLockQueue<int> Q;
Q.Produce(2);
Q.Produce(6);

int a;
Q.Consume(a);
cout<< a << endl;
Q.Consume(a);
cout<< a << endl;

return 0;
}
1

これはおそらく2010年のある時点で書いたもので、さまざまな参考文献の助けを借りて確信しています。マルチプロデューサーシングルコンシューマー。

template <typename T>
class MPSCLockFreeQueue 
{
private:
    struct Node 
    {
        Node( T val ) : value(val), next(NULL) { }
        T value;
        Node* next;
    };
    Node * Head;               
    __declspec(align(4)) Node * InsertionPoint;  //__declspec(align(4)) forces 32bit alignment this must be changed for 64bit when appropriate.

public:
    MPSCLockFreeQueue() 
    {
        InsertionPoint = new Node( T() );
        Head = InsertionPoint;
    }
    ~MPSCLockFreeQueue() 
    {
        // release the list
        T result;
        while( Consume(result) ) 
        {   
            //The list should be cleaned up before the destructor is called as there is no way to know whether or not to delete the value.
            //So we just do our best.
        }
    }

    void Produce( const T& t ) 
    {
        Node * node = new Node(t);
        Node * oldInsertionPoint = (Node *) InterLockedxChange((volatile void **)&InsertionPoint,node);
        oldInsertionPoint->next = node;
    }

    bool Consume( T& result ) 
    {
        if (Head->next)
        {
            Node * oldHead = Head;
            Head = Head->next;
            delete oldHead;
            result = Head->value;
            return true;
        }       
        return false;               // else report empty
    }

};
0

私はcで書かれた別の解決策を見つけました:

http://www.ddj.com/hpc-high-performance-computing/2195002

0
RED SOFT ADAIR