スレッド間で同期する必要がある非常に単純なデータのコンテナーを作成しました。最高のパフォーマンスが欲しい。ロックは使いたくない。
「リラックスした」アトミックを使いたい。部分的には、その少し余分な活力のためであり、部分的にはそれらを本当に理解するためです.
私はこれに多くの作業をしてきましたが、このコードが私が投げたすべてのテストに合格するところまで来ました。しかし、それは完全な「証拠」ではないので、私が見逃しているものがあるかどうか、またはこれをテストできる他の方法があるかどうか疑問に思っていますか?
ここに私の前提があります:
- ノードが適切にプッシュおよびポップされ、スタックが決して無効にされないことだけが重要です。
- メモリ内の操作の順序は、1 つの場所でのみ重要であると私は信じています。
- compare_exchange 操作自体の間。これは、アトミックが緩和されていても保証されます。
- 「ABA」問題は、ポインタに識別番号を追加することで解決されます。32 ビット システムでは、これにはダブルワードの compare_exchange が必要であり、64 ビット システムでは、ポインタの未使用の 16 ビットが ID 番号で埋められます。
- したがって、スタックは常に有効な状態になります。 (右?)
これが私が考えていることです。「通常」、私たちが読んでいるコードについて推論する方法は、それが書かれた順序を見ることです。メモリは「順不同」に読み書きできますが、プログラムの正確性を無効にする方法ではありません。
マルチスレッド環境ではそれが変わります。それがメモリフェンスの目的です - コードを見て、それがどのように機能するかを推測できるようにするためです。
ここですべてが順不同になる可能性がある場合、リラックスしたアトミックで何をしているのでしょうか? ちょっと遠すぎませんか?
私はそうは思いませんが、それが私がここで助けを求めている理由です.
compare_exchange 操作自体は、相互に順次の一貫性を保証します。
アトミックへの読み取りまたは書き込みが行われる他の唯一の時間は、compare_exchange の前にヘッドの初期値を取得することです。変数の初期化の一部として設定されます。私が知る限り、この操作が「適切な」値を返すかどうかは関係ありません。
現在のコード:
struct node
{
node *n_;
#if PROCESSOR_BITS == 64
inline constexpr node() : n_{ nullptr } { }
inline constexpr node(node* n) : n_{ n } { }
inline void tag(const stack_tag_t t) { reinterpret_cast<stack_tag_t*>(this)[3] = t; }
inline stack_tag_t read_tag() { return reinterpret_cast<stack_tag_t*>(this)[3]; }
inline void clear_pointer() { tag(0); }
#elif PROCESSOR_BITS == 32
stack_tag_t t_;
inline constexpr node() : n_{ nullptr }, t_{ 0 } { }
inline constexpr node(node* n) : n_{ n }, t_{ 0 } { }
inline void tag(const stack_tag_t t) { t_ = t; }
inline stack_tag_t read_tag() { return t_; }
inline void clear_pointer() { }
#endif
inline void set(node* n, const stack_tag_t t) { n_ = n; tag(t); }
};
using std::memory_order_relaxed;
class stack
{
public:
constexpr stack() : head_{}{}
void push(node* n)
{
node next{n}, head{head_.load(memory_order_relaxed)};
do
{
n->n_ = head.n_;
next.tag(head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
}
bool pop(node*& n)
{
node clean, next, head{head_.load(memory_order_relaxed)};
do
{
clean.set(head.n_, 0);
if (!clean.n_)
return false;
next.set(clean.n_->n_, head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
n = clean.n_;
return true;
}
protected:
std::atomic<node> head_;
};
他の質問と比べて、この質問の何が違うのですか? リラックスしたアトミック。彼らは質問に大きな違いをもたらします。
それで、あなたはどう思いますか?足りないものはありますか?