std::atomic_flag
C++11 开始正式加入了多线程库及原子操作,原子操作是无锁并发的基础。所谓原子操作,就是具有原子性的操作:该操作对外不可分割。
std::atomic_flag
std::atomic_flag
是 C++ 唯一保证内部无锁的原子类型,其内部维护一个布尔值。
标准
31.10 Flag type and operations [atomics.flag]
namespace std
{
struct atomic_flag
{
constexpr atomic_flag() noexcept;
atomic_flag(const atomic_flag &) = delete;
atomic_flag &operator=(const atomic_flag &) = delete;
atomic_flag &operator=(const atomic_flag &) volatile = delete;
bool test(memory_order = memory_order::seq_cst) const volatile noexcept;
bool test(memory_order = memory_order::seq_cst) const noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) volatile noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) noexcept;
void clear(memory_order = memory_order::seq_cst) volatile noexcept;
void clear(memory_order = memory_order::seq_cst) noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const volatile noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const noexcept;
void notify_one() volatile noexcept;
void notify_one() noexcept;
void notify_all() volatile noexcept;
void notify_all() noexcept;
};
}
- The atomic_flag type provides the classic test-and-set functionality. It has two states, set and clear.
- Operations on an object of type atomic_flag shall be lock-free. The operations should also be address-free.
-
The atomic_flag type is a standard-layout struct. It has a trivial destructor.
constexpr atomic_flag::atomic_flag() noexcept;
-
Effects : Initializes *this to the clear state.
bool atomic_flag_test(const volatile atomic_flag* object) noexcept; bool atomic_flag_test(const atomic_flag* object) noexcept; bool atomic_flag_test_explicit(const volatile atomic_flag* object, memory_order order) noexcept; bool atomic_flag_test_explicit(const atomic_flag* object, memory_order order) noexcept; bool atomic_flag::test(memory_order order = memory_order::seq_cst) const volatile noexcept; bool atomic_flag::test(memory_order order = memory_order::seq_cst) const noexcept;
- For atomic_flag_test, let order be memory_order::seq_cst.
- Preconditions : order is neither memory_order::release nor memory_order::acq_rel.
- Effects : Memory is affected according to the value of order.
-
Returns : Atomically returns the value pointed to by object or this.
bool atomic_flag_test_and_set(volatile atomic_flag* object) noexcept; bool atomic_flag_test_and_set(atomic_flag* object) noexcept; bool atomic_flag_test_and_set_explicit(volatile atomic_flag* object, memory_order order) noexcept; bool atomic_flag_test_and_set_explicit(atomic_flag* object, memory_order order) noexcept; bool atomic_flag::test_and_set(memory_order order = memory_order::seq_cst) volatile noexcept; bool atomic_flag::test_and_set(memory_order order = memory_order::seq_cst) noexcept;
- Effects : Atomically sets the value pointed to by object or by this to true. Memory is affected according to the value of order. These operations are atomic read-modify-write operations (6.9.2).
-
Returns : Atomically, the value of the object immediately before the effects.
void atomic_flag_clear(volatile atomic_flag* object) noexcept; void atomic_flag_clear(atomic_flag* object) noexcept; void atomic_flag_clear_explicit(volatile atomic_flag* object, memory_order order) noexcept; void atomic_flag_clear_explicit(atomic_flag* object, memory_order order) noexcept; void atomic_flag::clear(memory_order order = memory_order::seq_cst) volatile noexcept; void atomic_flag::clear(memory_order order = memory_order::seq_cst) noexcept;
- Preconditions : The order argument is neither memory_order::consume, memory_order::acquire, nor memory_order::acq_rel.
-
Effects : Atomically sets the value pointed to by object or by this to false. Memory is affected according to the value of order.
void atomic_flag_wait(const volatile atomic_flag* object, bool old) noexcept; void atomic_flag_wait(const atomic_flag* object, bool old) noexcept; void atomic_flag_wait_explicit(const volatile atomic_flag* object, bool old, memory_order order) noexcept; void atomic_flag_wait_explicit(const atomic_flag* object, bool old, memory_order order) noexcept; void atomic_flag::wait(bool old, memory_order order = memory_order::seq_cst) const volatile noexcept; void atomic_flag::wait(bool old, memory_order order = memory_order::seq_cst) const noexcept;
- For atomic_flag_wait, let order be memory_order::seq_cst. Let flag be object for the non-member functions and this for the member functions.
- Preconditions : order is neither memory_order::release nor memory_order::acq_rel.
-
Effects : Repeatedly performs the following steps, in order:
- (15.1) — Evaluates
flag->test(order) != old
. - (15.2) — If the result of that evaluation is true, returns.
- (15.3) — Blocks until it is unblocked by an atomic notifying operation or is unblocked spuriously.
- (15.1) — Evaluates
-
Remarks : This function is an atomic waiting operation (31.6).
void atomic_flag_notify_one(volatile atomic_flag* object) noexcept; void atomic_flag_notify_one(atomic_flag* object) noexcept; void atomic_flag::notify_one() volatile noexcept; void atomic_flag::notify_one() noexcept;
- Effects : Unblocks the execution of at least one atomic waiting operation that is eligible to be unblocked (31.6) by this call, if any such atomic waiting operations exist.
-
Remarks : This function is an atomic notifying operation (31.6).
void atomic_flag_notify_all(volatile atomic_flag* object) noexcept; void atomic_flag_notify_all(atomic_flag* object) noexcept; void atomic_flag::notify_all() volatile noexcept; void atomic_flag::notify_all() noexcept;
- Effects : Unblocks the execution of all atomic waiting operations that are eligible to be unblocked (31.6) by this call.
- Remarks : This function is an atomic notifying operation (31.6).
31.10 标志类型和操作 [atomics.flag]
namespace std
{
struct atomic_flag
{
constexpr atomic_flag() noexcept;
atomic_flag(const atomic_flag &) = delete;
atomic_flag &operator=(const atomic_flag &) = delete;
atomic_flag &operator=(const atomic_flag &) volatile = delete;
bool test(memory_order = memory_order::seq_cst) const volatile noexcept;
bool test(memory_order = memory_order::seq_cst) const noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) volatile noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) noexcept;
void clear(memory_order = memory_order::seq_cst) volatile noexcept;
void clear(memory_order = memory_order::seq_cst) noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const volatile noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const noexcept;
void notify_one() volatile noexcept;
void notify_one() noexcept;
void notify_all() volatile noexcept;
void notify_all() noexcept;
};
}
- atomic_flag 类型提供传统的 test-and-set 功能。它有两个状态,设置(set)和清除(clear)。
- 对 atomic_flag 类型的成员的任何操作都应是无锁的。这些操作也应是地址安全的(address-free)。
-
atomic_flag 类型是一个标准布局结构体,它有一个平凡的析构器。
constexpr atomic_flag::atomic_flag() noexcept;
-
作用 : 初始化 *this 是将值设置为清除(clear)。
bool atomic_flag_test(const volatile atomic_flag* object) noexcept; bool atomic_flag_test(const atomic_flag* object) noexcept; bool atomic_flag_test_explicit(const volatile atomic_flag* object, memory_order order) noexcept; bool atomic_flag_test_explicit(const atomic_flag* object, memory_order order) noexcept; bool atomic_flag::test(memory_order order = memory_order::seq_cst) const volatile noexcept; bool atomic_flag::test(memory_order order = memory_order::seq_cst) const noexcept;
- 对于 atomic_flag_test,假设 order 是 memory_order::seq_cst。
- 先决条件 : order 既不是 memory_order::release 也不是 memory_order::acq_rel。
- 作用 : 内存根据 order 的值被改变。
-
结果 : 原子的返回指向 object 或者 this 的值。
bool atomic_flag_test_and_set(volatile atomic_flag* object) noexcept; bool atomic_flag_test_and_set(atomic_flag* object) noexcept; bool atomic_flag_test_and_set_explicit(volatile atomic_flag* object, memory_order order) noexcept; bool atomic_flag_test_and_set_explicit(atomic_flag* object, memory_order order) noexcept; bool atomic_flag::test_and_set(memory_order order = memory_order::seq_cst) volatile noexcept; bool atomic_flag::test_and_set(memory_order order = memory_order::seq_cst) noexcept;
- 作用 : 原子的修改指向 object 或者 this 的值为 true。内存根据 order 的值被改变。这些操作原子的进行 read-modify-write 操作 (6.9.2)。
-
结果 : 原子的,对象的值在影响之前。
void atomic_flag_clear(volatile atomic_flag* object) noexcept; void atomic_flag_clear(atomic_flag* object) noexcept; void atomic_flag_clear_explicit(volatile atomic_flag* object, memory_order order) noexcept; void atomic_flag_clear_explicit(atomic_flag* object, memory_order order) noexcept; void atomic_flag::clear(memory_order order = memory_order::seq_cst) volatile noexcept; void atomic_flag::clear(memory_order order = memory_order::seq_cst) noexcept;
- 先决条件 : order 既不是 memory_order::consume 也不是 memory_order::acquire 或 memory_order::acq_rel。
-
作用 : 原子的修改指向 object 或者 this 的值为 false。内存根据 order 的值被改变。
void atomic_flag_wait(const volatile atomic_flag* object, bool old) noexcept; void atomic_flag_wait(const atomic_flag* object, bool old) noexcept; void atomic_flag_wait_explicit(const volatile atomic_flag* object, bool old, memory_order order) noexcept; void atomic_flag_wait_explicit(const atomic_flag* object, bool old, memory_order order) noexcept; void atomic_flag::wait(bool old, memory_order order = memory_order::seq_cst) const volatile noexcept; void atomic_flag::wait(bool old, memory_order order = memory_order::seq_cst) const noexcept;
- 对于 atomic_flag_wait,假设 order 是 memory_order::seq_cst。假设 flag 是非成员函数的对象和 this 是成员函数的。
- 先决条件 : order 既不是 memory_order::release 也不是 memory_order::acq_rel。
-
作用 : 重复执行下列步骤,依据 order:
- (15.1) — 评估
flag->test(order) != old
。 - (15.2) — 如果结果为 true,返回。
- (15.3) — 锁定,直到它被一个原子通知操作解除锁定或被虚假地解除锁定。
- (15.1) — 评估
-
注释 : 这个函数是一个原子的等待操作 (31.6)。
void atomic_flag_notify_one(volatile atomic_flag* object) noexcept; void atomic_flag_notify_one(atomic_flag* object) noexcept; void atomic_flag::notify_one() volatile noexcept; void atomic_flag::notify_one() noexcept;
- 作用 : 解除至少一个原子等待操作的执行,如果有任何这样的原子等待操作存在,该调用有资格被解除封锁(31.6)。
-
注释 : 这个函数是一个原子的通知操作 (31.6)。
void atomic_flag_notify_all(volatile atomic_flag* object) noexcept; void atomic_flag_notify_all(atomic_flag* object) noexcept; void atomic_flag::notify_all() volatile noexcept; void atomic_flag::notify_all() noexcept;
- 作用 : 解除所有有资格被此调用解锁的原子等待操作的执行 (31.6)。
- 注释 : 这个函数是一个原子的通知操作 (31.6)。
注释
- 根据 cppreference 的文章 成员函数,volatile 修饰成员函数的目的是对 this* 进行限定,类似于 const 修饰的成员函数只能调用该类的 const 修饰的成员函数。
- 根据 cppreference 的文章 destructor,平凡析构代表着,析构函数非虚且不是用户提供的,平凡析构函数是不进行任何动作的析构函数。有平凡析构函数的对象不要求 delete 表达式,并可以通过简单地解分配其存储进行释放。
- 关于 std::memory_order 的内容将在后半部分讲述。
总结
目的
std::atomic_flag 是原子类型的最小概念的实现,默认值为 clear(false)。
在 C++20 之前,std::atomic_flag 必须使用 ATOMIC_FLAG_INIT 这个宏进行初始化,但是标准委员会意识到声明 std::atomic_flag 而不进行初始化没有任何意义,并且增加了无效代码,所以 C++20 开始,std::atomic_flag 被默认初始化为清除,通过构造函数 (D.25.1)。
C++11 后,C++20 之前,std::atomic_flag 只有 3 类成员函数,由于 std::atomic_flag 实际上为布尔值,所以这三个函数的参数只有 std::memory_order ,不需要传入参数,因此 std::atomic_flag 是一个非常轻量的标志:
- bool test(memory_order) :返回当前值
- bool test_and_set(memory_order) :将值设置为 true 并返回先前值
- void clear(memory_order):将值设置为 false
C++ 并发编程实战第二版中提出了一种使用 std::atomic_flag 的自旋锁类型:
class spinlock_mutex
{
std::atomic_flag flag{};
public:
void lock()
{
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
这个非常基础的自旋锁可以配合 std::lock_guard 来使用:
- 锁作为类成员在构造时被初始化
- 在对类的其他成员进行修改前,尝试上锁
- 上锁成功后执行修改操作
- 修改完成后解锁
由于 test_and_set 会返回之前的锁的状态,因此在 while 循环中,只有真正解锁(clear)才会停止循环,由于 test_and_set 具有原子性,所以 test_and_set 并不会破坏锁原本的状态。
遗憾的是,C++ 并发编程实战写于 2018 年并在 2019 年 2 月出版,而 C++20 恰好对 std::atomic_flag 做了改进:加入了 wait,notify_one,notify_all 函数,增强了 std::atomic_flag 的功能。
class wait_mutex
{
std::atomic_flag flag{};
public:
void lock()
{
while (flag.test_and_set(std::memory_order_acquire)){
flag.wait(flag.test(std::memory_order_acquire), std::memory_order_acquire);
}
}
void unlock()
{
flag.clear(std::memory_order_release);
flag.notify_one();
}
};
改进版的代码中添加了一个 wait 语句来让 while 循环等待,在解锁时发送停止等待的通知。