2016-08-10 5 views
1
#include <iostream> 

#include <atomic> 
#include <memory> 


template<typename T> 
class LockFreeQueue { 
public: 
    struct CountedNode; 

private: 
    std::atomic<CountedNode> head; 
public: 
    struct Node{ 
     explicit Node(const T& d) : next(CountedNode()), data(std::make_shared<T>(d)), node_counter(0) { } 
     std::atomic<CountedNode> next; 
     std::shared_ptr<T> data; 
     std::atomic<unsigned> node_counter; 
    }; 
    struct CountedNode { 
     CountedNode() noexcept : node(nullptr), counter(0) {} 
     explicit CountedNode(const T& data) noexcept : node(new Node(data) /* $4 */), counter(0) {} 
     Node* node; 
     int counter; 
    }; 


    void push(const T& data) 
    { 
     CountedNode new_node(data), curr, incrementedNext, next /*($2) */; 
     CountedNode empty; /*($3) */ 
     if (head.compare_exchange_strong(empty, new_node)) std::cout << "EQUALS\n"; // $1 
     else std::cout << "NOT EQUALS\n"; 

     if (head.compare_exchange_strong(next, new_node)) std::cout << "EQUALS\n"; // $1 
     else std::cout << "NOT EQUALS\n"; 
    } 

}; 


int main() { 
    LockFreeQueue<int> Q; 
    Q.push(2); 

    return 0; 
} 



    int main(){ 
    LockFreeQueue<int> Q; 
    Q.push(2); 

    return 0; 
    } 

それはエラーなしでコンパイルされ、実行されます。しかし、私は以下で説明する問題はまだあります。私の目には同じクラスの同じインスタンスですが、動作が異なります。おそらくUB

http://coliru.stacked-crooked.com/a/1fe71fafc5dde518

、それが期待されていない結果: NOTEQUALS は、私は上記のコード切れ端で野生の問題を抱えている

に等しいです。

特に、$1という行の比較が問題になります。つまり、最初はtrueを返すはずですが、この比較は常にfalseを返します。

私は混乱していたので、私はemptyheadのメモリを調べて、実際には異なっています。 head0x00000000 0x00000000 0x00000000 0x00000000(バイトになると)に等しく、OKと思われます。しかし、emptyは、 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7fと等しくなります。より興味深いのは、$2next0x00000000 0x00000000 0x00000000 0x00000000に等しいので、実際にはheadと同じです。しかし、例えば、curr,incrementedNext0x00000000 0x00000000 0x00000000 0x7f7f7f7f7fに等しい。 その動作は未定義であるため、未定義の動作が想定されますが、その理由は何ですか?私が正しく行っていないことは、この行動を教えてください。

P.S.私は$4のメモリリークについて知っていますが、今は無視しています。

g++ -latomic main.cpp -std=c++14でコンパイルしました。 私のgccのバージョンは6.1.0です。私もgcc 5.1.0でテストしました。結果は同じです。

@PeterCordesによって作成されたソースへのリンク:https://godbolt.org/g/X02QV8

+1

あなたは(https://stackoverflow.com [ちょうどこれを尋ねました]/questions/38862289/same-class-but-different-behaviour-probable-ub)のように、 –

+0

はい、削除して新しい(正しい)投稿を作成しました。 – Gilgamesz

+0

@KerrekSB、それは問題ですか?結局、私は以前のものを削除しました。 – Gilgamesz

答えて

4

パディング。 std::atomic::compare_exchange*は、2つのオブジェクトのメモリ表現を、memcmpのように比較します。構造体にパディングがある場合、その内容は不定であり、メンバワイズに等しい場合でも2つのインスタンスが区別されるようにすることができます(CountedNodeoperator==も定義しません)。

64ビットビルドでは、counterの後にパディングがあり、問題が表示されます。 32ビットビルドでは、そうではありません。そうではありません。

EDIT:私は今、間違っていると信じています。完全性のためだけに保存​​されます。 std::atomic_initはパディングを0にすることはありません。この例は偶然にしか動作しないように見えます。


head(だけでなく、Node::next)はstd::atomic_initで初期化する必要があります。代わりにそれと

std::atomic_init(&head, CountedNode()); 

your example works as expected

+0

動作しません。問題は依然として存在します。あなたのリンクは、パディングの内容が不確定で、あなたのコードの部分に正確に 'empty'のパディングがゼロに等しいという事実のためにのみ機能します。しかし、スタックのビューを変更すると(35行目のリンクを参照)、もう一度 'NOT EQUAL'と表示されます。http://coliru.stacked-crooked.com/a/d6a5c66734862e91 私はそれを "空の"メモリに "マニュアル"書き込みで解決しようとしました。それは助けになりました。つまり、私は意味します:http://coliru.stacked-crooked.com/a/56c9c69622fb3492 – Gilgamesz

+0

1.その状況で、 :: atomic_initとそれは必要ですか? 2.パディングに手動で書き込むよりも良い方法でその問題を解決するにはどうすればよいですか? – Gilgamesz

+0

実際には、 'std :: atomic_init'は本当に助けにならないと思う - それは単に原子を持つ' memcpy'引数ですが、引数には埋め込みがゼロになっているとは言いません。だから基本的に 'std :: atomic 'のように見えます。ここで 'T'は整数型やポインタ型以外のものです。 –

関連する問題