この記事では、<atomic>ヘッダーファイルの最も単純なアトミックタイプ、atomic_flagを紹介します。atomic_flagテストアンドセットとクリアの2つの操作のみをサポートする単純なアトミックブール型。
std :: atomic_flagコンストラクター
std :: atomic_flagコンストラクターは次のとおりです。
- atomic_flag()noexcept =デフォルト;
- atomic_flag(const atomic_flag&T)=削除;
std :: atomic_flagにはデフォルトのコンストラクターのみがあり、コピーコンストラクターは無効になっているため、他のstd :: atomic_flagオブジェクトから新しいstd :: atomic_flagオブジェクトを構築することはできません。
初期化中にATOMIC_FLAG_INIT初期化が明示的に使用されていない場合、新しく作成されたstd :: atomic_flagオブジェクトの状態は指定されません(設定もクリアもされません)。さらに、atomic_flagをコピーまたは移動することはできません。割り当て。
ATOMIC_FLAG_INIT:std :: atomic_flagオブジェクトがこのマクロで初期化されている場合、std :: atomic_flagオブジェクトが作成されたときにクリア状態であることが保証されます。
簡単な例を見てみましょう。main()関数はカウント用に10個のスレッドを作成します。カウントタスクを完了するスレッドは最初に独自のIDを出力し、その後カウントタスクを完了するスレッドは独自のIDを出力しません。
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread> // std::thread, std::this_thread::yield
#include <vector> // std::vector
std::atomic<bool> ready(false); // can be checked without being set
std::atomic_flag winner = ATOMIC_FLAG_INIT; // always set when checked
void count1m(int id)
{
while (!ready) {
std::this_thread::yield();
} // 等待主线程中设置 ready 为 true.
for (int i = 0; i < 1000000; ++i) {
} // 计数.
// 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.
// 此后其他线程执行 test_and_set 是 if 语句判断为 false,
// 因此不会输出自身 ID.
if (!winner.test_and_set()) {
std::cout << "thread #" << id << " won!\n";
}
};
int main()
{
std::vector<std::thread> threads;
std::cout << "spawning 10 threads that count to 1 million...\n";
for (int i = 1; i <= 10; ++i)
threads.push_back(std::thread(count1m, i));
ready = true;
for (auto & th:threads)
th.join();
return 0;
}
std :: atomic_flag :: test_and_setの概要
std :: atomic_flagのtest_and_set関数プロトタイプは次のとおりです。
bool test_and_set (memory_order sync = memory_order_seq_cst) volatile noexcept;
bool test_and_set (memory_order sync = memory_order_seq_cst) noexcept;
test_and_set()関数は、std :: atomic_flagフラグをチェックします。std:: atomic_flagが以前に設定されていない場合は、std :: atomic_flagフラグを設定し、std :: atomic_flagオブジェクトが設定されているかどうかを返します(std: :atomic_flagオブジェクトが設定されている場合はtrueを返し、そうでない場合はfalseを返します。
テストアンドセット操作はアトミックです(したがって、テストアンドセットはアトミックな読み取り-変更-書き込み(RMW)操作です)。
test_and_setはメモリ順序を指定できます(以下の記事でC ++ 11メモリ順序を詳しく紹介します。完全を期すためにtest_and_setパラメータsyncの値を示します)。値は次のとおりです
。簡単な例:
#include <iostream> // std::cout
#include <atomic> // std::atomic_flag
#include <thread> // std::thread
#include <vector> // std::vector
#include <sstream> // std::stringstream
std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;
void append_number(int x)
{
while (lock_stream.test_and_set()) {
}
stream << "thread #" << x << '\n';
lock_stream.clear();
}
int main()
{
std::vector<std::thread> threads;
for (int i = 1; i <= 10; ++i)
threads.push_back(std::thread(append_number, i));
for (auto & th:threads)
th.join();
std::cout << stream.str() << std::endl;;
return 0;
}
std :: atomic_flag :: clear()はじめに
std :: atomic_flagオブジェクトのフラグビットを除いて、つまり、atomic_flagの値をfalseに設定します。clear関数のプロトタイプは次のとおりです。
void clear (memory_order sync = memory_order_seq_cst) volatile noexcept;
void clear (memory_order sync = memory_order_seq_cst) noexcept;
std :: atomic_flagフラグをクリアすると、std :: atomic_flag :: test_and_setへの次の呼び出しでfalseが返されます。
std :: atomic_flag :: clear()は、上記の表に示す値を使用して、メモリの順序を指定できます。
std :: atomic_flag :: test_and_set()とstd :: atomic_flag :: clear()を組み合わせると、std :: atomic_flagオブジェクトを単純なスピンロックとして使用できます。次の例を参照してください。
#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void f(int n)
{
for (int cnt = 0; cnt < 100; ++cnt) {
while (lock.test_and_set(std::memory_order_acquire)) // acquire lock
; // spin
std::cout << "Output from thread " << n << '\n';
lock.clear(std::memory_order_release); // release lock
}
}
int main()
{
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f, n);
}
for (auto& t : v) {
t.join();
}
}
上記のプログラムでは、std :: atomic_flagオブジェクトロックのロック操作はlock.test_and_set(std :: memory_order_acquire);と理解でき、ロック解除操作はlock.clear(std :: memory_order_release)と同等です。
ロック時にlock.test_and_setがfalseを返す場合、ロックのフラグビットが以前にfalseであるため(つまり、ロックをロックしているスレッドがないため)、ロックが成功したことを意味します(この時点ではスピン状態にはなりません)。 、しかし、test_and_setを呼び出した後、ロックフラグはtrueであり、特定のスレッドが正常にロックを取得したことを示します。
スレッドのロックが解除される前に別のスレッドもlock.test_and_set(std :: memory_order_acquire)を呼び出して(つまり、lock.clear(std :: memory_order_release)を呼び出して)ロックを取得しようとすると、test_and_set(std :: memory_order_acquire)はtrueの場合、スピン状態に入ります。ロックを取得したスレッドがロック解除されている場合(つまり、lock.clear(std :: memory_order_release)を呼び出した後)、スレッドはlock.test_and_set(std :: memory_order_acquire)を呼び出そうとし、falseを返します。その後、whileは入力されません。スピンスレッドがロックを正常に取得したことを示します。
上記の分析によると、std :: atomic_flagオブジェクトは、特定の状況下で単純なスピンロックとして使用できることがわかっています。