В этой статье представлен простейший атомарный тип в заголовочном файле <atomic>: atomic_flag. atomic_flag Простой атомарный логический тип, который поддерживает только две операции: test-and-set и clear.
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 потоков для подсчета. Поток, который завершает задачу подсчета, сначала выводит свой собственный идентификатор, а поток, который завершает задачу подсчета, впоследствии не выводит свой собственный идентификатор:
#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
Прототип функции test_and_set для std :: atomic_flag выглядит следующим образом:
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.
Операция test-and-set является атомарной (так что test-and-set - это атомарная операция чтения-изменения-записи (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, это означает, что блокировка успешна (пока не войдет в состояние вращения в это время), потому что бит флага блокировки раньше был ложным (то есть ни один поток не блокировал блокировку) , Но после вызова test_and_set флаг блокировки становится истинным, указывая, что определенный поток успешно получил блокировку.
Если другой поток также вызывает lock.test_and_set (std :: memory_order_acquire) до того, как поток будет разблокирован (то есть вызывает lock.clear (std :: memory_order_release)), чтобы попытаться получить блокировку, тогда test_and_set (std :: memory_order_acquire) возвращает истина, затем в то время как переходит в состояние вращения. Если поток, получивший блокировку, разблокирован (то есть после вызова lock.clear (std :: memory_order_release)), поток пытается вызвать lock.test_and_set (std :: memory_order_acquire) и возвращает false, тогда while не войдет спин. Указывает, что поток успешно получил блокировку.
Согласно приведенному выше анализу мы знаем, что объект std :: atomic_flag может использоваться в качестве простой спин-блокировки при определенных обстоятельствах.