std::atomic<T>::compare_exchange_weak, std::atomic<T>::compare_exchange_strong
| (1) | (C++11以上) | |
bool compare_exchange_weak( T& expected, T desired, std::memory_order success, std::memory_order failure ) noexcept; |
||
bool compare_exchange_weak( T& expected, T desired, std::memory_order success, std::memory_order failure ) volatile noexcept; |
||
| (2) | (C++11以上) | |
bool compare_exchange_weak( T& expected, T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept; |
||
bool compare_exchange_weak( T& expected, T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept; |
||
| (3) | (C++11以上) | |
bool compare_exchange_strong( T& expected, T desired, std::memory_order success, std::memory_order failure ) noexcept; |
||
bool compare_exchange_strong( T& expected, T desired, std::memory_order success, std::memory_order failure ) volatile noexcept; |
||
| (4) | (C++11以上) | |
bool compare_exchange_strong( T& expected, T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept; |
||
bool compare_exchange_strong( T& expected, T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept; |
||
std::memcmp によって行われたかのように、 *this と expected のオブジェクト表現 (C++20未満)値表現 (C++20以上)をアトミックに比較し、それらがビット単位で等しければ、前者を desired で置き換えます (読み込み-変更-書き込み操作を行います)。 そうでなければ、 *this に格納されている実際の値を expected に読み込みます (ロード操作を行います)。 コピーは std::memcpy によって行われたかのように行われます。
読み込み-変更-書き込み操作およびロード操作に対するメモリモデルは、それぞれ success および failure です。 (2) および (4) のバージョンでは、読み込み-変更-書き込み操作とロード操作の両方に対して order が使用されます。 ただし order == std::memory_order_acq_rel または order == std::memory_order_release の場合、ロード操作に対してはそれぞれ std::memory_order_acquire および std::memory_order_relaxed が使用されます。
引数
| expected | - | アトミックオブジェクトに格納されていることが期待される値を指す参照 |
| desired | - | 期待通りの場合にアトミックオブジェクトに格納する値 |
| success | - | 比較が成功した場合の読み込み-変更-書き込み操作に対するメモリ同期順序付け。 すべての値を指定できます。 |
| failure | - | 比較が失敗した場合のロード操作に対するメモリ同期順序付け。 std::memory_order_release および std::memory_order_acq_rel は指定できません。 また、 success より強い順序付けを指定することもできません。 (C++17未満)
|
| order | - | 両方の操作に対するメモリ同期順序付け |
戻り値
ベースとなるアトミック値の変更に成功した場合は true、そうでなければ false。
ノート
比較およびコピーはビット単位です (std::memcmp および std::memcpy と同様です)。 コンストラクタ、代入演算子、比較演算子は使用されません。
weak 版の関数 (1-2) は、スプリアスに失敗することが許されています。 つまり、等しい場合でも *this != expected であるかのように動作することがあります。 compare-and-exchange がループ内にある場合、プラットフォームによっては weak 版の方が良い性能を発揮します。
weak 版の compare-and-exchange ではループが必要だけれども strong 版ではそうでないというときは、 T のオブジェクト表現にパディングビットや (C++20未満)トラップビットが含まれていたり、同じ値に対して複数のオブジェクト表現を提供する (浮動小数点の NaN など) 可能性がなければ、 strong 版の方が好まれます。 それらの可能性がある場合は、一般的に weak 版の compare-and-exchange の方が、何らかの安定したオブジェクト表現に急速に収束するため、上手く行きます。
いくつかのメンバの値表現に寄与するけれども他のメンバの値表現には寄与しないビットを持つ共用体の場合、アクティブなメンバの値表現に寄与しないそのようなパディングビットが不定値を持つせいで compare-and-exchange が必ず失敗することがあります。
|
オブジェクトの値表現に決して寄与しないパディングビットは無視されます。 |
(C++20以上) |
例
compare-and-exchange 操作は、ロックフリーデータ構造の基本的な構成要素として、よく使われます。
#include <atomic>
template<typename T>
struct node
{
T data;
node* next;
node(const T& data) : data(data), next(nullptr) {}
};
template<typename T>
class stack
{
std::atomic<node<T>*> head;
public:
void push(const T& data)
{
node<T>* new_node = new node<T>(data);
// put the current value of head into new_node->next
new_node->next = head.load(std::memory_order_relaxed);
// now make new_node the new head, but if the head
// is no longer what's stored in new_node->next
// (some other thread must have inserted a node just now)
// then put that new head into new_node->next and try again
while(!head.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed))
; // the body of the loop is empty
// Note: the above use is not thread-safe in at least
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
// MSVC prior to 2014-03-17 (bug 819819). The following is a workaround:
// node<T>* old_head = head.load(std::memory_order_relaxed);
// do {
// new_node->next = old_head;
// } while(!head.compare_exchange_weak(old_head, new_node,
// std::memory_order_release,
// std::memory_order_relaxed));
}
};
int main()
{
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
}
compare_exchange_strong がどのようにアトミック変数の値または比較に使用した値を変更するかをデモンストレーションします。
| This section is incomplete Reason: more practical use of the strong CAS would be nice, such as where Concurrency in Action uses it |
#include <atomic>
#include <iostream>
std::atomic<int> ai;
int tst_val= 4;
int new_val= 5;
bool exchanged= false;
void valsout()
{
std::cout << "ai= " << ai
<< " tst_val= " << tst_val
<< " new_val= " << new_val
<< " exchanged= " << std::boolalpha << exchanged
<< "\n";
}
int main()
{
ai= 3;
valsout();
// tst_val != ai ==> tst_val is modified
exchanged= ai.compare_exchange_strong( tst_val, new_val );
valsout();
// tst_val == ai ==> ai is modified
exchanged= ai.compare_exchange_strong( tst_val, new_val );
valsout();
}
出力:
ai= 3 tst_val= 4 new_val= 5 exchanged= false
ai= 3 tst_val= 3 new_val= 5 exchanged= false
ai= 5 tst_val= 3 new_val= 5 exchanged= true
関連項目
| アトミックに、アトミックオブジェクトの値を非アトミック引数と比較し、等しければ交換を行い、等しくなければ読み込みます (関数テンプレート) |