std::atomic_flag是最简单的标准原子类型,它代表一个bool状态。这个状态只有两个值:set和clear。到目前为止,我还没发现在在哪使用它,除非在特殊的场景下。
std::atomic_flag对象必须被ATOMIC_FLAG_INIT初始化,它会将std::atomic_flag初始化为clear状态。这是没有选择余地的,atomic_flag总是以clear状态初始化。
它是唯一一个被要求这样初始化,也是唯一一个保证是lock_free的原子类型。如果atomic_flag具有静态存储生命期,那么它会保证静态初始化,这就意味着没有初始化顺序问题,它总是能够在第一次操作时初始化。
一旦一个atomic_flag被初始化,那么你只能对它做三件事:销毁它、clear、或者是set并获得上一次的值,分别对应于他的析构函数、clear()函数、set_and_test()函数。clear()和set_and_test()函数都可以指定内存指令。clear()是个存储操作,set_and_test()是个read-modify-write操作。它们的缺省内存指令都是memory_order_seq_cst。
看下面示例:
f.clear(std::memory_order_release);
bool x=f.test_and_set();
atomic_flag对象不支持拷贝构造和拷贝赋值,这是所有原子类型都需要遵守的规则。所有针对原子类型的操作都是原子的,而拷贝构造和赋值运算涉及到两个对象。一个涉及两个原子对象的操作就不再是一个原子操作了,因为在拷贝构造和拷贝赋值操作中需要读取前一个对象然后再对第二个对象赋值。这是来自两个独立对象的独立操作,合并到一起就不是原子操作了。
这些限制使atomic_flag很适合作为一种自旋锁mutex(spinlock mutex)。初始时atomic_flag的状态为clear,对应于unlocked状态。要想锁定mutex,需要循环执行set_and_test()函数,直到得到的值为false,以确保当前线程把值设置为了true。unlock操作就是简单的调用clear()函数。一个自旋锁的实现:
class spinlock_mutex
{
std::atomic_flag flag = ATOMIC_FLAG_INIT;//原书中的代码无法编译通过,改为这样初始化
public:
spinlock_mutex()
{}
void lock()
{
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
这个锁很基础,但是已经足够为lock_guard<>使用。它的lock函数的本质就是一直等待,因此这是一种带有争论的糟糕选择,不过它足以确保互斥。
std::atomic_flag的限制太多了以至于无法被当做一个bool型使用,因为它没有一个非改变的值请求操作。为此,我们应该使用std::atomic<bool>。