- atomic库的底层也是有锁的,仅 std::atomic_flag 底层是不需要锁来辅助完成 atomic 操作,其他的类型包括 atomic<int> 、atomic<bool> 。。。 等在内部基本都是通过锁的机制来完成 atomic。不过具体情况要视编译器和操作系统而定,至少c++ 11标准中对于这块的要求是 expected ,而不是 required。
- std::atomic_flag f=ATOMIC_FLAG_INIT; 是固定用法,即std::atomic_flag只能用ATOMIC_FLAG_INIT初始化,且默认状态是clear的。这是所有atomic类型中唯一有硬性要求的,同时flag也是唯一 100% lock-free的。
- 所有的atomic 类型都是 no-copy(拷贝构造和拷贝赋值都被禁用) 和 no-move(移动构造和移动赋值都被禁用)的。但除了 flag 之外,其他都可以使用对应的 noatomic 类型值进行赋值。
比如 atomic<bool> a_b = false;atomic<int> a_i = 10; 这些都是合法的 ;
但是 atomic<bool> a_b1 = false;atomic<bool> a_b2 = a_b1 这个就是非法的;
但是 atomic_flag a_f = true 就是非法的,atomic_flag 只能用 std::atomic_flag f=ATOMIC_FLAG_INIT ,可以武断的说它的赋值符号只能是这个值。
- std::atomic_flag 的 test_and_set() 用来将 flag 设置为 true,同时通过返回值通知调用者之前的那个值是 false 还是 true,乍一看这个操作是不是应该就叫做 set 比较合适,但是细想确发现 test 确实是有作用的,因为std::atomic_flag 是保证 lock-free的,因此会执行的非常快,因为任何线程都不需要等待其他线程对于这个标志的操作。所以大家对于 std::atomic_flag 的操作都会被立刻响应,这恰恰造成一个问题,那就是调用者不知道是自己设置成的true?还是别人干的?所以这个函数通过返回值的方式告诉调用者,如果返回false,那么表示这次改变其实是由当前操作完成的,如果返回true,则说明这个标志在我们设置它之前就已经是true的了,其实我们没有做功。我们可以利用这个及时来实现一个自旋锁:
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag(ATOMIC_FLAG_INIT)
{}
void lock()
{
//如果test_and_set返回false,则说明是当前线程改变了标志位,那么相当于拿到锁了
//因此跳出while循环,这也是自旋锁的语意 “ 反复忙等检查,直到条件满足 ”,atomic_flag
//的 100% lock-free 特性正好满足 “忙等”
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
- std::atomic_flag在 c++ 20才引入 test() 函数,之前只有 构造、析构、clear、test_and_set。因此c++ 20之前我们没法在不修改flag的状态下查看当前的 flag值,所以除了自旋锁场景外,我们多会选择 atomic<bool>