アトミック ブール値

残りのアトミックは、std::atomic_flag とは対照的に、クラス テンプレート std::atomic の部分的または完全な特殊化です。 std::atomic.

から始めましょう。

std::atomic

std::atomic には、std::atomic_flag よりも多くの機能があります。明示的に true または false に設定できます。 2 つのスレッドを同期するには、これで十分です。したがって、アトミック変数を使用して条件変数をシミュレートできます。

まず、条件変数を見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// conditionVariable.cpp

#include <condition_variable>
#include <iostream>
#include <thread>
#include <vector>

std::vector<int> mySharedWork;
std::mutex mutex_;
std::condition_variable condVar;

bool dataReady;

void waitingForWork(){
 std::cout << "Waiting " << std::endl;
 std::unique_lock<std::mutex> lck(mutex_);
 condVar.wait(lck,[]{return dataReady;});
 mySharedWork[1]= 2;
 std::cout << "Work done " << std::endl;
}

void setDataReady(){
 mySharedWork={1,0,3};
 {
 std::lock_guard<std::mutex> lck(mutex_);
 dataReady=true;
 }
 std::cout << "Data prepared" << std::endl;
 condVar.notify_one();
}

int main(){
 
 std::cout << std::endl;

 std::thread t1(waitingForWork);
 std::thread t2(setDataReady);

 t1.join();
 t2.join();
 
 for (auto v: mySharedWork){
 std::cout << v << " ";
 }
 
 
 std::cout << "\n\n";
 
}

そして今、アトミックブール値を持つペンダントです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// atomicCondition.cpp

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

std::vector<int> mySharedWork;
std::atomic<bool> dataReady(false);

void waitingForWork(){
 std::cout << "Waiting " << std::endl;
 while ( !dataReady.load() ){ // (3)
 std::this_thread::sleep_for(std::chrono::milliseconds(5));
 }
 mySharedWork[1]= 2; // (4)
 std::cout << "Work done " << std::endl;
}

void setDataReady(){
 mySharedWork={1,0,3}; // (1)
 dataReady= true;  // (2)
 std::cout << "Data prepared" << std::endl;
}

int main(){
 
 std::cout << std::endl;

 std::thread t1(waitingForWork);
 std::thread t2(setDataReady);

 t1.join();
 t2.join();
 
 for (auto v: mySharedWork){
 std::cout << v << " ";
 }
 
 
 std::cout << "\n\n";
 
}

17行目が14行目の後に実行されることを保証するものは何ですか?または、より一般的に言えば、スレッド t2 が mySharedWork={1,0,3} (22 行目) を実行した後、スレッド t1 が mySharedWork[1]=2 (17 行目) を実行します。よりフォーマルになりました。

  • Line22 (1) 前に起こる 23行目 (2)
  • 14 行目 (3) 前に起こる 17 行目 (4)
  • 23 行目 (2) 同期 14 行目 (3)
  • なぜなら 前に起こる mySharedWork={1,0,3} (1) は推移的です。 前発生 mySharedWork[1]=2 (4)

一点だけ明示したい。条件変数 condVar またはアトミック dataReady により、共有変数 mySharedWork へのアクセスが同期されます。これは、mySharedWork がロックまたはアトミックによって保護されていないにもかかわらず保持されます。

どちらのプログラムも、mySharedWork に対して同じ結果を生成します。

プッシュ対プルの原則

明らかに、私は少しだまされました。条件変数とアトミック ブール値を使用したスレッドの同期には、1 つの違いがあります。条件変数は、待機中のスレッド (condVar.notify()) に、作業を続行する必要があることを通知します。しかし、アトミック ブール値を持つ待機中のスレッドは、送信者がその作業を完了したかどうかを確認します (dataRead=true)。

条件変数は、待機中のスレッドに通知します (プッシュ原則)。アトミック ブール値は繰り返し値を要求します (プル原理)。

compare_exchange_strong と compare_exchange_weak

std::atomic および std::atomic の完全または部分的な特殊化は、すべてのアトミック操作のパンとバターをサポートします:compare_exchange_strong.この関数の構文は、bool compare_exchange_strong(T&expected, T&desired) です。この操作は 1 つのアトミック操作で値を比較および交換するため、多くの場合、compare_and_swap (CAS) と呼ばれます。この種の操作は、利用可能な多くのプログラミング言語で行われています。もちろん、動作は多少異なる場合があります。

atomicValue.compare_exchange_strong(expected, desired) の呼び出しは、次の戦略に従います。 atomicValue と expected のアトミック比較が true を返す場合、atomicValue の値は同じアトミック操作で desired に設定されます。比較で false が返された場合、expected は atomicValue に設定されます。操作compare_exchange_strongが強いと呼ばれる理由は簡単です。メソッドcompare_exchange_weakがあります。この脆弱なバージョンは、誤って失敗する可能性があります。つまり、*atomicValue ==が成り立つことが期待されますが、弱いバリアントは false を返します。そのため、ループ内で条件を確認する必要があります:while ( !atomicValue.compare_exchange_weak(expected, desired) )。弱いフォームの理由はパフォーマンスです。一部のプラットフォームでは、weak は strong バリアントよりも高速です。

次は?

次の投稿は、クラス テンプレート std::atomic についてです。そこで、積分とポインタのさまざまな特殊化について書きます。これらは、アトミック ブール値 std::atomic よりも豊富なインターフェイスを提供します。 (校正者 Alexey Elymanov )